“卓见杯”第五届CCPC中国大学生程序设计竞赛河南省赛 题解

题目链接

A 最大下降矩阵 <dp>

最长上升子序列的变形。
令f[i]表示以i为结尾的最长非递减子序列长度,每次转移遍历一整排数字,如果都满足再进行转移。

#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
 
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int N = 1000;
ll g[N][N];
int f[N]; //以i为结尾的最长长度
 
int main()
{
#ifdef LOCAL
    freopen("C:/input.txt", "r", stdin);
#endif
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
    	for (int j = 1; j <= m; ++j)
        	scanf("%lld", &g[i][j]);
    int ans = 0;
    for (int i = 1; i <= n; ++i)
    {
        f[i] = 1;
        for (int j = 1; j < i; ++j)
        {
            int flag = 1;
            for (int k = 1; k <= m; ++k)
            if (g[j][k] <= g[i][k])
            {
                flag = 0;
                break;
            }
            if (flag)
                f[i] = max(f[i], f[j] + 1);
        }
        ans = max(ans, f[i]);
    }
    cout << n - ans << endl;
 
    return 0;
}

B 树上逆序对 <主席树> <树状数组>

问题分成两个部分,每个节点造成的逆序对数量和查询子树逆序对数量。
对于第一部分,由于题目要求添加新的点操作,使用主席树维护当前节点到根节点的链上有多少个节点大于当前节点值,也就是当前节点能与祖先节点组成的逆序对数量。
这样维护的好处是即使添加节点也不会对之前的结果造成影响,只需要计算新的节点即可。
维护的方法是对每个子节点建立一颗副本权值线段树,继承父节点的的权值信息并插入当前节点的值,这样父节点的其它子树不会对当前节点造成影响。
第二部分,对每个点查询祖先节点有多少个大于当前节点值的点,把这个数量按照DFS插入树状数组中。对于每次询问则使用树状数组区间求和,整个树的和-删去的子树和。

#include <stdio.h>
#include <bits/stdc++.h>
#define fst first
#define sed second
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int MOD = 1e9 + 7;
template<typename T1, typename T2> inline bool setmin(T1 &a, const T2 &b){ if (b >= a) return false; a = b; return true; }
template<typename T1, typename T2> inline bool setmax(T1 &a, const T2 &b){ if (b <= a) return false; a = b; return true; }
template<typename T1, typename T2> inline void addmod(T1 &a, const T2 &b){ a = (a + b) % MOD; }
const int N = 2e5 + 10;
int n, m;
int a[N]; //节点值 插入从n+1开始
int t[N], u[N], fz[N]; //询问类型 节点
ll ans[N];
vector<int> e[N];

vector<int> dz;
int Diz(int x)
{
	return lower_bound(dz.begin(), dz.end(), x) - dz.begin();
}
struct node //主席树维护每个节点到根节点权值出现次数
{
	int val; //当前管辖区间数值出现次数
	int ls, rs;
}tre[N * 40];
int root[N], idx; //树的每个节点对应的线段树根节点

void Update(int &x, int y, int l, int r, int v)
{
	x = ++idx;
	tre[x] = tre[y], ++tre[x].val;
	if (l == r)
		return;
	int m = l + r >> 1;
	if (v <= m)
		Update(tre[x].ls, tre[y].ls, l, m, v);
	else
		Update(tre[x].rs, tre[y].rs, m + 1, r, v);
}
int Query(int x, int l, int r, int pl, int pr) //1~x链上累积版本 值[pl, pr]出现次数
{
	if (pl <= l && r <= pr)
		return tre[x].val;
	int m = l + r >> 1, res = 0;
	if (m >= pl)
		res += Query(tre[x].ls, l, m, pl, pr);
	if (m < pr)
		res += Query(tre[x].rs, m + 1, r, pl, pr);
	return res;
}
ll c[N]; //树状数组 在dfs序上维护每个点到根的逆序数量
inline int lowbit(int x)
{
	return x & -x;
}
void Add(int x, int v)
{
	while (x < N)
		c[x] += v, x += lowbit(x);
}
ll Ask(int x) //查询1~x前缀和
{
	ll res = 0;
	while (x)
		res += c[x], x -= lowbit(x);
	return res;
}
int l[N], r[N], tot; //dfs序
void DFS(int x, int f)
{
	l[x] = ++tot;
	if (x <= n) //原有节点
	{
		Update(root[x], root[f], 1, dz.size(), Diz(a[x])); //再父节点线段树的基础上开一条新链并插入当前值
		ll res = Query(root[x], 1, dz.size(), Diz(a[x]) + 1, dz.size()); //查询大于自身值数量
		Add(l[x], res); //将自身逆序数量加到自身dfs序的位置
	}
	for (int y : e[x])
		DFS(y, x);
	r[x] = tot;
}
int main()
{
#ifdef LOCAL
	//freopen("C:/input.txt", "r", stdin);
#endif
	cin >> n >> m;
	for (int i = 1; i <= n; ++i)
		scanf("%d", &a[i]), dz.push_back(a[i]);
	for (int i = 2; i <= n; ++i)
	{
		int f;
		scanf("%d", &f);
		e[f].push_back(i);
	}
	int num = n; //节点编号
	for (int i = 1; i <= m; ++i) //离线询问
	{
		scanf("%d%d", &t[i], &u[i]);
		if (t[i] == 1)
		{
			scanf("%d", &a[++num]); //数值节点都要存在num位置
			e[u[i]].push_back(num);
			fz[num] = u[i]; //记录父节点
			dz.push_back(a[num]);
		}
	}
	dz.push_back(-INF), dz.push_back(INF); //编号从1开始 每个值+1都不会越界
	sort(dz.begin(), dz.end());
	dz.erase(unique(dz.begin(), dz.end()), dz.end());
	DFS(1, 0);
	num = n; //重新计算编号。。
	for (int i = 1; i <= m; ++i)
	{
		if (t[i] == 1) //新增节点陆续建树并插入树状数组
		{
			//Update(root[++num], root[fz[num]], 1, dz.size(), Diz(a[num])); //前++函数传参编译标准不同???
			++num;
			Update(root[num], root[fz[num]], 1, dz.size(), Diz(a[num])); //再父节点线段树的基础上开一条新链并插入当前值
			ll res = Query(root[num], 1, dz.size(), Diz(a[num]) + 1, dz.size()); //查询大于自身值数量
			Add(l[num], res); //将自身逆序数量加到自身dfs序的位置
		}
		else
		{
			ll res = Ask(tot) - Ask(r[u[i]]) + Ask(l[u[i]] - 1); //不包含子树的答案
			printf("%lld\n", res);
		}
	}

	return 0;
}

C 大小接近的点对 <树状数组> | <主席树>

树状数组版:
使用树状数组查询某个范围内的数值的数量,因为数值比较大需要先进行离散化处理。
使用DFS遍历整棵树,当到达某个节点时首先查询区间[a[i]-m, a[i]+m]范围内的数字数量记为last,表示还没到当前子树时已有的数量。
将当前节点值加进梳妆数组,因为自身到自身也算。进行递归,回溯后再次查询区间[a[i]-m, a[i]+m]记为now,表示增加了自身子树之后的数量。
最后每个点的答案f[x]加上每个儿子的f[y],再加上now-last表示子树节点能够和当前节x点形成接近点对,即为每个点的答案。

#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
 
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e5 + 10;
int n, m;
int a[N];
vector<int> e[N];
int c[N];
ll f[N];
 
inline int lowbit(int x)
{
    return x & -x;
}
void Add(int x, int v)
{
    while (x < N)
        c[x] += v, x += lowbit(x);
}
int Ask(int x)
{
    if (x >= N)
        return 0;
    int res = 0;
    while (x)
        res += c[x], x -= lowbit(x);
    return res;
}
int RangeAsk(int l, int r)
{
    return Ask(r) - Ask(l - 1);
}
vector<int> dz;
int Diz(int x)
{
    return lower_bound(dz.begin(), dz.end(), x) - dz.begin();
}
void DFS(int x)
{
    int l = Diz(a[x] - m), r = Diz(a[x] + m);
    if (r >= dz.size() || dz[r] > a[x] + m) //离散化后的数值不一定出现需要特判
        --r;
    ll last = RangeAsk(l, r); //非子树的相近点数量
    Add(Diz(a[x]), 1);
    for (int y : e[x])
        DFS(y), f[x] += f[y];
    ll now = RangeAsk(l, r); //原有+子树的
    f[x] += now - last; //相减后即为子树的
}
int main()
{
#ifdef LOCAL
    freopen("C:/input.txt", "r", stdin);
#endif
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]), dz.push_back(a[i]);
    dz.push_back(-INF); //离散化 编号从1开始
    sort(dz.begin(), dz.end());
    dz.erase(unique(dz.begin(), dz.end()), dz.end());
    for (int i = 2; i <= n; ++i)
    {
        int x;
        scanf("%d", &x);
        e[x].push_back(i);
    }
    DFS(1);
    for (int i = 1; i <= n; ++i)
        printf("%lld\n", f[i]);
 
    return 0;
}

主席树版:
DFS整棵树,按照DFS序将每个点的值插入主席树内,每个点查询当前DFS序区间内与当前点的值相差不超过k的数量。
最后每个点再加上子树的答案即为当前点答案。

#include <stdio.h>
#include <bits/stdc++.h>
#define fst first
#define sed second
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int MOD = 1e9 + 7;
template<typename T1, typename T2> inline bool setmin(T1 &a, const T2 &b){ if (b >= a) return false; a = b; return true; }
template<typename T1, typename T2> inline bool setmax(T1 &a, const T2 &b){ if (b <= a) return false; a = b; return true; }
template<typename T1, typename T2> inline void addmod(T1 &a, const T2 &b){ a = (a + b) % MOD; }
const int N = 1e5 + 10;
int a[N], n, k;
vector<int> e[N];
int l[N], r[N], num; //dfs序
ll ans[N];

vector<int> dz;
int Diz(int x)
{
	return lower_bound(dz.begin(), dz.end(), x) - dz.begin();
}
struct node
{
	int val;
	int ls, rs;
}tre[N * 40];
int root[N], idx;

void Update(int &x, int y, int l, int r, int v) //插入一个数值v lr函数传参
{
	x = ++idx;
	tre[x] = tre[y], ++tre[x].val;
	if (l == r)
		return;
	int m = l + r >> 1;
	if (v <= m)
		Update(tre[x].ls, tre[y].ls, l, m, v);
	else
		Update(tre[x].rs, tre[y].rs, m + 1, r, v);
}
int Query(int x, int y, int l, int r, int pl, int pr) //查询数值[pl, pr]出现次数
{
	if (pl <= l && r <= pr) //完全包含
		return tre[x].val - tre[y].val; //版本差
	int m = l + r >> 1, res = 0;
	if (m >= pl) //和左区间有交
		res += Query(tre[x].ls, tre[y].ls, l, m, pl, pr); 
	if (m < pr)
		res += Query(tre[x].rs, tre[y].rs, m + 1, r, pl, pr);
	return res;
}
ll DFS(int x)
{
	l[x] = ++num;
	Update(root[l[x]], root[l[x] - 1], 1, dz.size(), Diz(a[x])); //以dfs序编号位置插入离散化的值
	ll res = 0; //子树贡献
	for (int y : e[x])
		res += DFS(y);
	r[x] = num;
	int L = Diz(a[x] - k), R = Diz(a[x] + k); //离散化后的范围
	if (R >= dz.size() || dz[R] > a[x] + k) //可能不存在a[x]+k
		--R;
	return ans[x] = res + Query(root[r[x]], root[l[x] - 1], 1, dz.size(), L, R); //查询当前子树数值[L, R]的节点数量
}
int main()
{
#ifdef LOCAL
	freopen("C:/input.txt", "r", stdin);
#endif
	cin >> n >> k;
	for (int i = 1; i <= n; ++i)
		scanf("%d", &a[i]), dz.push_back(a[i]);
	dz.push_back(-INF);
	sort(dz.begin(), dz.end());
	dz.erase(unique(dz.begin(), dz.end()), dz.end());
	for (int i = 2; i <= n; ++i)
	{
		int f;
		scanf("%d", &f);
		e[f].push_back(i);
	}
	DFS(1);
	for (int i = 1; i <= n; ++i)
		printf("%lld\n", ans[i]);

	return 0;
}

F 咕咕的计数题 II <数学>

将l除以a得到第一个可能再答案范围内的除数L,r除以a得到最后一个可能再答案范围内的除数R。
分为3部分,1除数在[L, R]范围,第一个和最后一个的位置可能只有一部分和lr相交需要特判。
2除了第一个则后面的每个除数对应除数个满足条件的数字,如5 7 19,第一个为[10, 11]长度为10/5,第二个[15,17]长度为15/5。这一部分进行等差数列求和即可。但是这种情况在除数大于等于a时会发生相交。
3所以最后一部分如果计算范围大于等于a*a则后面的所有数字都会覆盖。

#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
 
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
ll a, l, r;
 
int main()
{
#ifdef LOCAL
    freopen("C:/input.txt", "r", stdin);
#endif
    int T;
    cin >> T;
    while (T--)
    {
        scanf("%lld%lld%lld", &a, &l, &r);
        ll L = l / a, R = r / a; //bei
        ll res = 0;
        if (L < a)
            res += max(0LL, min(a * L + L - 1, r) - max(a * L, l) + 1);
        ll L1 = L + 1, R1 = min(R - 1, a - 1);
        if (L1 <= R1)
            res += (L1 + R1) * (R1 - L1 + 1) / 2;
        if (a <= r / a)
            res += (r - max(l, a * a) + 1);
        else if(R > L)
            res += max(0LL, min(a * R + R - 1, r) - R * a + 1);
        printf("%lld\n", res);
    }
 
    return 0;
}

G 咕咕的 01 图 <贡献>

考虑边权全部为 1 的情况,事实上就是奇数度数结点的个数 2,那么考虑按照边权相同去处
理所有边即可。有个更直观的想法,同样是边权相同的一并处理,那么把o看成点,--
成是边,那么o--o--o--o--o本身可以断裂为o-,-o-,-o-,-o-,-o,在这之中只有-o-是不会对答案有贡献的。

边权不同的不能同时处理,所以分开计算。

#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int MOD = 1e9 + 7;
template<typename T1, typename T2> inline bool setmin(T1 &a, const T2 &b){ if (b >= a) return false; a = b; return true; }
template<typename T1, typename T2> inline bool setmax(T1 &a, const T2 &b){ if (b <= a) return false; a = b; return true; }
template<typename T1, typename T2> inline void addmod(T1 &a, const T2 &b){ a = (a + b) % MOD; }

const int N = 1e6 + 10;

struct node
{
	int u, w; //起点 边权
	bool operator < (const node &o) const
	{
		if (u != o.u)
			return u < o.u; //优先按起点排序
		return w < o.w;
	}
}e[N * 2];

int main()
{
#ifdef LOCAL
	//freopen("C:/input.txt", "r", stdin);
#endif
	int T;
	cin >> T;
	while (T--)
	{
		int n, m;
		scanf("%d%d", &n, &m);
		for (int i = 0; i < m; ++i)
		{
			int u, v, w;
			scanf("%d%d%d", &u, &v, &w);
			e[i * 2] = { u, w };
			e[i * 2 + 1] = { v, w };
		}
		m *= 2; //双倍边
		e[m] = { 0, 0 };
		sort(e, e + m);
		ll ans = 0;
		for (int i = 0; i < m; ++i)
			if (e[i].u == e[i + 1].u && e[i].w == e[i + 1].w) //同一个点 代价相同
				++i; //跳过两条边并不计算代价
			else
				ans += e[i].w; //只有一个则计算代价
		printf("%lld\n", ans / 2);
	}

	return 0;
}

H 咕咕的搜索序列 <LCA> <模拟>

一个遍历顺序可以看作是从当前点x跳跃到下一个点y,然后检测x跳跃到y是否合法,如果所有的跳跃都合法则整个序列合法。
x和y不相等时有三种情况。
1、x是y的子树节点,如果这时候跳跃到y则说明,y包含x的这颗子树全部访问完毕,所以使用DFS将整个子树全部标记。
2、y是x的子树节点,这种情况是非法的,按照题意不可能先显示祖先节点再显示子节点。
3、x和y互相不为祖先节点,这时候如果跳跃到y则,x和y的LCA的x那颗子树全部访问完毕,这时使用LCA算法求得x最接近LCA的那个点并将整个子树进行标记。
对于每次显示,如果显示该节点则当前节点的整个子树被访问完毕进行DFS标记,如果当前显示节点已经被标记则说明非法。
DFS标记时如果遇见某个节点已经被访问就不再进行递归,因为当前节点被访问肯定子树也被访问了。

#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int MOD = 1e9 + 7;
template<typename T1, typename T2> inline bool Min(T1 &a, const T2 &b){ if (b >= a) return false; a = b; return true; }
template<typename T1, typename T2> inline bool Max(T1 &a, const T2 &b){ if (b <= a) return false; a = b; return true; }
template<typename T1, typename T2> inline void adm(T1 &a, const T2 &b){ a = (a + b) % MOD; }

const int N = 1e6 + 10;
int n, m;
int a[N]; //所给访问序列
int fz[N], dep[N];
vector<int> e[N];
bool vis[N];

void DEP(int x, int f)
{
	dep[x] = dep[f] + 1;
	for (int y : e[x])
		DEP(y, x);
}
int LCA(int x, int y) //找到x上跳一次就是x和y的lca的位置
{
	while (dep[x] < dep[y] && fz[y] != x) //调整到同一高度但是不等于另一节点
		y = fz[y];
	while (dep[x] > dep[y] && fz[x] != y)
		x = fz[x];
	while (fz[x] != y && fz[y] != x && fz[x] != fz[y])
		x = fz[x], y = fz[y];
	return x; //结果和原x相同则y为x子树节点 否则上跳1为xy的lca
}
void DFS(int x) //标记x的子树
{
	vis[x] = 1;
	for (int y : e[x])
		if (!vis[y]) //如果y被标记则y的子树也被标记无需递归
			DFS(y);
}
bool solve()
{
	int x = a[1], y; //当前节点 转移节点
	for (int i = 2; i <= m; ++i)
	{
		DFS(x); //每次标记当前子树
		y = a[i];
		if (vis[y]) //已经被访问
			return false;
		int a = LCA(x, y); 
		DFS(a); //跳跃到lca的令一个子树则a的子树被访问完毕
		x = y;
		/*
		if (a == x) //不可能再次访问子树节点
			return false;
		*/
	}
	return true;
}
int main()
{
#ifdef LOCAL
	freopen("C:/input.txt", "r", stdin);
#endif
	int T;
	cin >> T;
	while (T--)
	{
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; ++i)
			e[i].clear(), vis[i] = 0;
		for (int i = 2; i <= n; ++i)
		{
			int f;
			scanf("%d", &f);
			fz[i] = f;
			e[f].push_back(i);
		}
		for (int i = 1; i <= m; ++i)
		{
			scanf("%d", &a[i]);
			if (a[i] > n || a[i] < 1)
			{
				printf("BAD GUGU\n");
				goto brk;
			}
		}
		DEP(1, 0);
		printf("%s\n", solve() ? "NOT BAD" : "BAD GUGU");
	brk:continue;
	}

	return 0;
}

I Childhood dream <枚举> <复杂度分析>

直接next_permutation函数找出所有可能的长度为m的串,每次所有给出的条件是否都满足,不满足就进行return。
因为每个数字都不相同,总复杂度10!*100*10虽然很大但是很多情况下判断不了几次就return了所以没有超时。
注意第一个数字可以为0

#include <stdio.h>
#include <bits/stdc++.h>
#define fst first
#define sed second
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int N = 110;
int x[N][N], y[N]; //y答案
int a[N], b[N];
int n, m;

inline bool check()
{
	for (int i = 1; i <= n; ++i)
	{
		int cnt[10] = { 0 }, A = 0, B = 0;
		for (int j = 0; j < m; ++j)
		{
			if (x[i][j] == y[j])
				++A;
			else //位置不一样
				++cnt[y[j]];
		}
		for (int j = 0; j < m; ++j)
			if (cnt[x[i][j]])
				++B, --cnt[x[i][j]];
		if (A != a[i] || B != b[i])
			return false;
	}
	return true;
}
int main()
{
#ifdef LOCAL
	freopen("C:/input.txt", "r", stdin);
#endif
	cin >> n >> m;
	for (int i = 1; i <= n; ++i)
	{
		char s[N];
		scanf("%s%d%d", s, &a[i], &b[i]);
		for (int j = 0; j < m; ++j)
			x[i][j] = s[j] - '0';
	}
	for (int i = 0; i < 10; ++i) //第一位可以为0
		y[i] = i;
	do 
	{
		if (check())
		{
			for (int i = 0; i < m; ++i)
				printf("%d", y[i]);
			cout << endl, exit(0);
		}
	} while (next_permutation(y, y + 10));

	return 0;
}

J THE END IS COMING!!! <费用流>

注意题目所给的k只能用于无法通过移动元素完成的点,普通的点不能使用。
对于每个元素互不影响,独立计算求和即可。难点在于建图,图建好了直接跑最小费用最大流即可。
建立两个点,0作为超级源点S,n*2+1作为超级汇点T。
源点S与每个点建立一条边,容量为题目中所需元素ci代价为1,表示题目的元素洞口给当前点提供了ci*1个元素。
每个点再虚拟出来一个i+n点,源点与这些虚拟点建立一条容量为ci代价为0的边提供流量,表示这些点在完成任务后元素可以再次利用,但是最大利用率不可能超过原有需求量ci。
这些i+n的虚拟点,与其他的普通点j,如果点i完成任务后仍有足够时间在j开启之前抵达则n+i向j建立一条容量为cj代价为0的点,表示重复利用i的元素运送到j。
最后对于每个点i向汇点T建立一条容量为ci代价为0的边,表示当前i点最多需要ci个元素,只有出洞的时候需要代价。

#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int MOD = 1e9 + 7;
template<typename T1, typename T2> inline bool Min(T1 &a, const T2 &b){ if (b >= a) return false; a = b; return true; }
template<typename T1, typename T2> inline bool Max(T1 &a, const T2 &b){ if (b <= a) return false; a = b; return true; }
template<typename T1, typename T2> inline void adm(T1 &a, const T2 &b){ a = (a + b) % MOD; }

const int N = 210;
const int M = 1e5 + 10;
int x[N], y[N], s[N], u[N], c[N][10]; //坐标 开启 用时 需求
bool z[N]; //无法完成的点
int dis[N], pre[N];
bool vis[N];

struct edge
{
	int v, w, c, nxt; //w容量 c代价
}e[M * 2];
int h[N], idx;

void AddEdge(int u, int v, int w, int c)
{
	e[idx] = { v, w, c, h[u] };
	h[u] = idx++;
}
bool SPFA(int st, int ed)
{
	queue<int> q;
	memset(dis, 0x3f, sizeof(dis));
	memset(pre, -1, sizeof(pre));
	dis[st] = 0;
	q.push(st), vis[st] = 1;
	while (!q.empty())
	{
		int u = q.front(); q.pop(), vis[u] = 0;
		for (int i = h[u]; ~i; i = e[i].nxt)
		{
			int v = e[i].v, w = e[i].w, c = e[i].c;
			if (dis[v] > dis[u] + c && w)
			{
				dis[v] = dis[u] + c;
				pre[v] = i;
				if (!vis[v])
					q.push(v), vis[v] = 1;
			}
		}
	}
	return pre[ed] != -1;
}
void MCMF(int st, int ed, int &cost, int &flow)
{
	cost = flow = 0;
	while (SPFA(st, ed))
	{
		int mi = INF;
		for (int i = pre[ed]; ~i; i = pre[e[i ^ 1].v])
			Min(mi, e[i].w);
		flow += mi;
		for (int i = pre[ed]; ~i; i = pre[e[i ^ 1].v])
		{
			e[i].w -= mi, e[i ^ 1].w += mi;
			cost += mi * e[i].c;
		}
	}
}
int main()
{
#ifdef LOCAL
	freopen("C:/input.txt", "r", stdin);
#endif
	int n, m, k, sx, sy;
	cin >> n >> m >> k >> sx >> sy;
	--n;
	for (int i = 1; i <= n; ++i)
	{
		scanf("%d%d%d%d", &x[i], &y[i], &s[i], &u[i]);
		for (int j = 0; j < m; ++j)
			scanf("%d", &c[i][j]);
		if (abs(x[i] - sx) + abs(y[i] - sy) > s[i]) //只有没法完成的点才能用k
			z[i] = 1, --k;
	}
	if (k < 0)
		cout << "THE END IS COMING!!!!!" << endl, exit(0); //神仙都救不了你
	int ans = 0;
	for (int p = 0; p < m; ++p) //每个颜色单独求解
	{
		idx = 0;
		memset(h, -1, sizeof(h));
		for (int i = 1; i <= n; ++i)
			if (!z[i]) //不处理无法完成的点
			{
				AddEdge(0, i, c[i][p], 1); //0作为源点 到达每个点容量为c代价1
				AddEdge(i, 0, 0, -1);
				AddEdge(0, i + n, c[i][p], 0); //每个点拆分一个虚拟点表示可以重复利用每个点的元素 源点给虚拟点供应c容量代价0
				AddEdge(i + n, 0, 0, 0);
				AddEdge(i, n * 2 + 1, c[i][p], 0); //n*2+1为超级汇点 容量c代价0
				AddEdge(n * 2 + 1, i, 0, 0);
				for (int j = 1; j <= n; ++j) 
					if (!z[i] && j != i && s[i] + u[i] + abs(x[i] - x[j]) + abs(y[i] - y[j]) <= s[j]) //i完成后能在j开始前抵达
					{
						AddEdge(n + i, j, INF, 0); //表示点j可以重复利用i的元素
						AddEdge(j, n + i, 0, 0);
					}
			}
		int cost = 0, flow = 0;
		MCMF(0, n * 2 + 1, cost, flow);
		ans += cost;
	}
	cout << ans << endl;

	return 0;
}
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值