“卓見杯”第五屆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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章