牛客图论 (第一章)


题目链接


A

二分图染色 , 重点是构造二分图的部分

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1E5 + 5;

int n;
int head[N * 2],ver[N * 4],nxt[N * 4],tot;
int col[N * 2];

void add(int x,int y) {
	ver[++tot] = y , nxt[tot] = head[x] , head[x] = tot;
}

void dey(int x,int c)
{
	col[x] = c;
	for(int i = head[x] ; i ; i = nxt[i]) {
		int y = ver[i];
		if(!col[y]) dey(y , 3 - c);
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0) ; cout.tie(0);
	
	cin >> n;
	vector<pair<int,int>> p(n + 1);
	
	for(int i = 1 ; i <= n ; ++i) {
		int x,y; cin >> x >> y;
		p[i].first = x ,  p[i].second = y;
		add(x , y) , add(y , x);
	}
	
	for(int i = i ; i <= n ; ++i) {
		add(2 * i - 1 , 2 * i) , add(2 * i , 2 * i - 1); //构造偶环
	}
	
	for(int i = 1 ; i <= 2 * n ; ++i) {
		if(col[i] == 0) {
			dey(i , 1);
		}	
	}
	
	for(int i = 1 ; i <= n ; ++i) {
		cout << col[p[i].first] << ' ' << col[p[i].second] << '\n'; 
	}
	
	return 0;
}

B

二分图最大匹配

#include <bits/stdc++.h>
using namespace std;
const int N = 55 , M = N * N;

int n;
int stu[N];
int stay[N],match[N * 2];
int head[N],ver[M],nxt[M],tot;
bool v[N * 2];

void add(int x,int y) {
	ver[++tot] = y , nxt[tot] = head[x] , head[x] = tot;
}
bool dfs(int x) {
	for(int i = head[x]; i ; i = nxt[i]) {
		int y = ver[i];
		if(v[y]) continue;
		v[y] = true;
		if(!match[y] || dfs(match[y])) {
			match[y] = x;
			return true;
		}
	}
	return false;
}
bool solve()
{
	tot = 0;
	memset(match , 0 , sizeof match);
	memset(head , 0 , sizeof head);
	
	cin >> n;
	for(int i = 1 ; i <= n ; ++i) 
		cin >> stu[i];
	
	int cnt = 0;
	for(int i = 1 ; i <= n ; ++i) {
		cin >> stay[i] ; stay[i] ^= 1;
		if(!stu[i] || stay[i]) ++cnt;
	}
	
	for(int i = 1 ; i <= n ; ++i) {
		for(int j = 1 ; j <= n ; ++j) {
			int x ; cin >> x; 
			if(!stu[i] || stay[i]) 
			{
				if(!stu[i]) {
					if(x && stu[j]) {
						add(i , j + n);
					}
				} else if(stay[i]) {
					if(i == j) {
						add(i , j + n);
					}
					else if(x && stu[j] ) {
						add(i , j + n);
					}
				}
			}
		}
	}
	
	int ans = 0;
	for(int i = 1 ; i <= n ; ++i) {
		if(!stu[i] || stay[i]) {
			memset(v , 0 , sizeof v);
			if(dfs(i)) ++ans;
		}
	}
	
	if(ans >= cnt) return true;
	return false;
}	

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0) ; cout.tie(0);
	
	int t;
	cin >> t;
	while (t--) {
		if(solve()) cout << "^_^\n";
		else cout << "T_T\n";
	}
	
	return 0;
}

C

\(KM\) 算法的板子题
算法理解看 \(lyd\) 的 , 板子的话感觉牛客上的更好记忆

#include <bits/stdc++.h>
using ll = long long; 

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	while (true) {
		
		int n,m;
		std::cin >> n >> m;
		if(!n && !m) break;
		
		std::vector<std::string> g(n);
		for(int i = 0 ; i < n ; ++i)
			std::cin >> g[i];
			
		std::vector<std::pair<int,int>> p,h;
		for(int i = 0 ; i < n ; ++i) {
			for(int j = 0 ; j < m ; ++j) {
				if(g[i][j] == 'm') p.push_back({i , j});
				if(g[i][j] == 'H') h.push_back({i , j}); 
			}
		}
		
		int sz = p.size();
		 
		std::vector<std::vector<int>> f(sz , std::vector<int>(sz));
		for(int i = 0 ; i < sz ; ++i)
			for(int j = 0 ; j < sz ; ++j) {
				f[i][j] = std::abs(p[i].first - h[j].first) + std::abs(p[i].second - h[j].second);
				f[i][j] = -f[i][j];
			}
		
		std::vector<bool> va(sz) , vb(sz);
		std::vector<int> match(sz , -1) , la(sz) , lb(sz);
		
		std::function<bool(int)> dfs = [&] (int x) {
			
			va[x] = true;
			for(int y = 0 ; y < sz ; ++y) {
				if(!vb[y] && la[x] + lb[y] == f[x][y]) {
					vb[y] = true;
					if(match[y] == -1 || dfs(match[y])) {
						match[y] = x ; return true;
					}
				}
			}
			return false;
		};
		
		//预处理顶值
		for(int i = 0 ; i < sz ; ++i) {
			la[i] = -(1 << 30) ; // -inf
			lb[i] = 0;
			for(int j = 0 ; j < sz ; ++j)
				la[i] = std::max(la[i] , f[i][j]);
		}
		
		for(int i = 0 ; i < sz ; ++i) { //对左部图的每一个点进行最大匹配 
			
			while (true) {
				
				for(int j = 0 ; j < sz ; ++j)
					va[j] = vb[j] = false;
				
				if(dfs(i)) break;
				
				int d = 1E9;
				for(int j = 0 ; j < sz ; ++j)
					if(va[j]) {
						for(int k = 0 ; k < sz ; ++k)
							if(!vb[k]) d = std::min(d , la[j] + lb[k] - f[j][k]);
					}
				
				for(int j = 0 ; j < sz ; ++j) {
					if(va[j]) la[j] -= d;
					if(vb[j]) lb[j] += d;
				}			
			}
		}
		
		int ans = 0;
		for(int i = 0 ; i < sz ; ++i)
			if(match[i] != -1) ans += f[match[i]][i];
			
		std::cout << -ans << '\n';
		
	}
}

D

对于陨石 \((x , y)\) \(x\)行 或 \(y\) 列放炸弹均能使这个陨石消失 , 这个条件使我们联想到最小点覆盖中边被任意一端的选中点覆盖均可认为这条边被覆盖 , 所以对于每个陨石的行列连边 (陨石作为边) , 做最小点覆盖即可

二分图最小点覆盖 = 二分图最大匹配

写板子时候想着为啥不用管 \(v[x]\) , 因为 \(y\) 走的匹配边是之前合法二分图匹配的匹配边 , \(y\)不可能连到已经走过的匹配点 \(x\) , 而 \(x\) 走非匹配边则可能走到之前的 \(y\)

#include <bits/stdc++.h>
using ll = long long;

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	int n,m;
	std::cin >> n >> m;

	std::vector<std::vector<int>> g(n);
	while (m--) {
		int x,y;
		std::cin >> x >> y;
		x-- ; y--;
		g[x].push_back(y);
	}
	
	std::vector<bool> v(n);
	std::vector<int> match(n , -1);
	std::function<bool(int)> dfs = [&] (int x) {
		for(auto y : g[x]) {
			if(!v[y]) {
				v[y] = true;
				if(match[y] == -1 || dfs(match[y])) {
					match[y] = x ; return true;
				}
			}
		}
		return false;		
	};
	
	int ans = 0;
	for(int i = 0 ; i < n ; ++i) {
		
		for(int j = 0 ; j < n ; ++j) 
			v[j] = false;
			
		if(dfs(i)) ++ans;
	}
	
	std::cout << ans << '\n';
	 
	return 0;
}

E

感觉难点想到不是直接抵达到题目要求的 “对角线全是黑色” ,
转换思路建图 : 如果能满足每一行每一列只有一个黑色块 , 我们就能由这个状态变为题目的状态
对于每一个黑点 \((x , y)\) , \(x\) 行与 \(y\) 列连边即可

为什么这样建边 , 反证即可 , 如果在使用移动之前不存在 \(n\) 个点的选出不满足 "每行每列只有一个黑色块",那么使用移动条件之后也无法达到这个状态

#include <bits/stdc++.h>
using ll = long long;

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	int t;
	std::cin >> t;
	
	while (t--) {
		int n;
		std::cin >> n;
		
		std::vector<bool> v(n);
		std::vector<int> match(n , -1);
		std::vector<std::vector<int>> g(n);
		
		std::function<bool(int)> dfs = [&] (int x) {
			for(auto y : g[x]) {
				if(!v[y]) {
					v[y] = true;
					if(match[y] == -1 || dfs(match[y])) {
						match[y] = x ; return true;
					} 
				}
			}
			return false;	
		};
		
		for(int i = 0 ; i < n ; ++i) 
			for(int j = 0 ; j < n ; ++j) {
				int x; std::cin >> x;
				if(x == 1) g[i].push_back(j); 
			}
			
		int ans = 0;
		for(int i = 0 ; i < n ; ++i) {
			for(int j = 0 ; j < n ; ++j)
				v[j] = false;	
			if(dfs(i)) ++ans;
		}
		
		if(ans == n) std::cout << "Yes\n";
		else std::cout << "No\n";
	}
	
	return 0;
}

F

注意二分图匹配的 "1要素" 与 "0要素"
"1要素" : 相邻两格之间有一条边相连
"0要素" : 将棋盘黑白染色之后 , 相同颜色的格子不会被一条边覆盖

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 105 , M = N * N * 4 , ID = N * N + N;

int n,m;
int head[ID],ver[M],nxt[M],tot;
bool ban[N][N];
int dx[]={1,-1,0,0},dy[]={0,0,1,-1};
int match[ID];
bool v[ID];

void add(int x,int y) {
	ver[++tot] = y , nxt[tot] = head[x] , head[x] = tot;
}
int get(int x,int y) {
	return x * N + y;
}
bool dfs(int x) {
	
	for(int i = head[x] ; i ; i = nxt[i]) {
		int y = ver[i];
		if(v[y]) continue;
		v[y] = true;
		if (!match[y] || dfs(match[y])) {
			match[y] = x;
			return true;	
		}
	}
	
	return false;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0) ; cout.tie(0);
	
	cin >> n >> m;
	
	while (m--) {
		int x,y;
		cin >> x >> y;
		ban[x][y] = true;
	}
	
	for(int x1 = 1 ; x1 <= n ; ++x1)
		for(int y1 = 1 ; y1 <= n; ++y1) {
			if(ban[x1][y1] || ( (x1 + y1) & 1) ) continue;
			for(int k = 0 ; k < 4 ; ++k) {
				int x2 = x1 + dx[k] , y2 = y1 + dy[k];
				if(x2 < 1 || x2 > n || y2 < 1 || y2 > n || ban[x2][y2]) continue;
				add(get(x1 , y1) , get(x2 , y2));
			}
		}
	
	int ans = 0;
	for(int x = 1 ; x <= n ; ++x)
		for(int y = 1 ; y <= n ; ++y){
			if(ban[x][y] || ( (x + y) & 1) ) continue;
			memset(v , 0 , sizeof v);
			if(dfs(get(x , y))) ++ans;
		}
		
	cout << ans << '\n';
	
	return 0;
}

G

贪心策略 : 因为圈能重叠 , 问题一定有解.圈能覆盖两个点的话尽量覆盖 , 剩下的单点单独花费一个圈的代价覆盖即可

对于第一个问题求最大匹配数,
答案即为 : 总黑点数 - 2 * 最大匹配 + 最大匹配 = 总黑点数 - 最大匹配

引理 : 最小路径覆盖(选择最少的边覆盖所有的点) = 总顶点数 - 最大匹配

每次 dfs 将访问数组清零

#include <bits/stdc++.h>
using ll = long long;
const int dx[]={-1,1,0,0},dy[]={0,0,-1,1};

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	int n,m;
	std::cin >> n >> m;

	std::vector<std::string> s(n);
	for(int i = 0 ; i < n ; ++i)
		std::cin >> s[i];
	
	int r = std::max(n , m);
	auto get = [&] (int x , int y) -> int {
		return x * r + y;
	};
	
	int MAXN = n * r + m , sum = 0; 
	std::vector<std::vector<int>> g(MAXN);
	for(int x = 0 ; x < n ; ++x)
		for(int y = 0 ; y < m ; ++y) {
			if(s[x][y] == '*') ++sum;
 			if( (x + y) % 2 == 0 && s[x][y] == '*') {
				int idx = get(x , y);
				for(int k = 0 ; k < 4 ; ++k) {
					int _x = x + dx[k] , _y = y + dy[k];
					if(_x < 0 || _x > n - 1 || _y < 0 || _y > m - 1 || s[_x][_y] != '*') continue;  
					g[idx].push_back(get(_x , _y));
				}
			}
		}
		
	
	std::vector<bool> v(MAXN);
	std::vector<int> match(MAXN , -1);
	
	std::function<bool(int)> dfs = [&](int x) {
		for(auto y : g[x]) {
			if(v[y]) continue; v[y] = true;
			if(match[y] == -1 || dfs(match[y])) {
				match[y] = x ; return true;	
			}
		}
		return false;	
	};
	
	int ans = 0;
	for(int x = 0; x < n ; ++x)
		for(int y = 0 ; y < m ; ++y) {
			if( (x + y) % 2 == 0 && s[x][y] == '*') {
				fill(v.begin(),v.end(),0);
				if(dfs(get(x , y))) ++ans;
			}		
		}
		
	std::cout << sum - ans << '\n';
	
	return 0;
} 

H

有向无环图最小不相加路劲覆盖

先假设每一个点都有一个机器人 , 如果存在 \(x\)\(y\) 的有向边 , 那么 \(y\) 点上的机器人就存在一个选择去除 ( \(x\)上的机器人走过去 ) , 这里拆点 , 用 \(x\) 的入点连接 \(y\) 的出点即可 , 最后用总点数 - 最大匹配

#include <bits/stdc++.h>
using ll = long long;

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	int n,m;
	std::cin >> n >> m;
	
	std::vector<std::vector<int>> g(2 * n);
	while (m--) {
		int u,v;
		std::cin >> u >> v;
		--u,--v;
		g[u].push_back(v + n);
	}
	
	std::vector<bool> v(2 * n);
	std::vector<int> match(2 * n , -1);
	
	std::function<bool(int)> dfs = [&](int x) {
		for(auto y : g[x]) {
			if(v[y]) continue;
			v[y] = true;
			if(match[y] == -1 || dfs(match[y])) {
				match[y] = x ; return true;
			}
		}	
		return false;
	};
	
	int ans = 0;
	for(int i = 0 ; i < n ; ++i) {
		fill(v.begin(),v.end(),0);
		if(dfs(i)) ++ans;	
	}
	
	std::cout << n - ans << '\n';
	return 0;
}

I

枚举哪一个点要成为中心点 , 确立中心点之后 , 考虑全部点(自己的话就考虑有没有自环)与中心点是否有双向连边,由于题目保证了没有重边,这一步操作只有加边的操作.接下来,将除了根节点的其余节点拆为"出点"与"入点",这样就构成了两边集合大小均为 \(n - 1\) 的二分图 , 最终我们希望的效果是二分图完美匹配(每一个点均有了一个出度与入度) , 那么先跑最大匹配 , 再讨论加边与删边的情况即可

#include <bits/stdc++.h>
using ll = long long;

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	int n,m;
	std::cin >> n >> m;
	
	std::set<int> g[n + 1];
	for(int i = 0 ; i < m ; ++i) {
		int u,v;
		std::cin >> u >> v;
		u-- , v--;
		g[u].insert(v);
	}
	
	int mn = 1E9;
	for(int rt = 0 ; rt < n ; ++rt) {
		
		int ans = 0 ;
		for(int i = 0 ; i < n ; ++i) {
			if(i == rt) {
				if(g[i].find(rt) == g[i].end()) ++ans; 
			} else {
				if(g[i].find(rt) == g[i].end()) ++ans;
				if(g[rt].find(i) == g[rt].end()) ++ans; 
			}
		}
		
		int sum = 0;
		std::vector<std::vector<int>> adj(n);
		for(int u = 0 ; u < n ; ++u) {
			if(u == rt) continue;
			for(auto v : g[u]) {
				if(v != rt) {
					++sum;
					adj[u].push_back(v + n);
				}
			}
		}
		
		std::vector<int> match(2 * n + 5 , -1);
		std::vector<bool> v(2 * n + 5);
		
		std::function<bool(int)> dfs = [&](int x) {
			for(auto y : adj[x]) {
				if(v[y]) continue;
				v[y] = true;
				if(match[y] == -1 || dfs(match[y])) {
					match[y] = x ; return true;
				}
			}	
			return false;
		};
		
		int cnt = 0;
		for(int i = 0 ; i < n ; ++i) {
			if(i == rt) continue;
			fill(v.begin(),v.end(),0);
			if(dfs(i)) ++cnt;
		}
		
		if(cnt < n - 1) ans += (n - 1) - cnt;
		if(sum >= cnt) ans += sum - cnt;
		
		mn = std::min(mn , ans);
	}
	
	std::cout << mn << '\n';
	
	return 0;
}

POJ2594

有向无环图最小相加路劲覆盖

对原图跑闭包 , 然后再做 H 的匹配即可

#include <bits/stdc++.h>
using ll = long long;

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	int n,m;
	while(std::cin >> n >> m && n) {
		
		std::vector<std::vector<int>> g(n , std::vector<int>(n , 0));
		for(int i = 0 ; i < m ; ++i) {
			int u,v;
			std::cin >> u >> v;
			--u , --v;
			g[u][v] = 1;
		}
		
		auto floyd = [&] (void) -> void {
			for(int k = 0 ; k < n ; ++k)
				for(int i = 0 ; i < n ; ++i)
					for(int j = 0 ; j < n ; ++j)
						if(g[i][k] && g[k][j]) g[i][j] = 1;
		};
		
		std::vector<bool> v(2 * n);
		std::vector<int> match(2 * n , -1);
		std::vector<std::vector<int>> adj(n);
		for(int i = 0 ; i < n ; ++i) {
			for(int j = 0 ; j < n ; ++j) {
				if(g[i][j]) {
					adj[i].push_back(j + n);
				}
			}
		}
		
		std::function<bool(int)> dfs = [&](int x) {
			for(auto y : adj[x]) {
				if(v[y]) continue; 
				v[y] = true;
				if(match[y] == -1 || dfs(match[y])) {
					match[y] = x ; return true;
				}
			}
			return false;	
		};
		
		int ans = 0;
		for(int i = 0 ; i < n ; ++i) {
			fill(v.begin(),v.end(),0);
			if(dfs(i)) ++ans;
		}
		
		std::cout << n - ans << '\n';
		
	}
	
	return 0;
}

补充题目

食物链 - 带权并查集

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 5E4 + 5 , M = 3;

int n,k;
int fa[N],d[N];

int find(int x) {
	if(fa[x] == x) return x;
	int pa = fa[x] , f = find(pa);
	d[x] = ( d[x] + d[pa] ) % M; //注意不是固定加 1 ,这里是 d[x] , 考虑的是到原来祖先节点的代价 
	fa[x] = f;
	return fa[x];
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0) ; cout.tie(0);
	
	cin >> n >> k;
	
	for(int i = 1 ; i <= n ; ++i) {
		fa[i] = i;
	}
	
	int res = 0;
	
	while (k--) {
		
		int opt,x,y;
		cin >> opt >> x >> y;
		int fx = find(x) , fy = find(y);	
		
		if(x > n || y > n || (opt == 2 && x == y) ) {
			++res ; continue;
		}
		
		if(opt == 1) {
			if(fx == fy) {
				if(d[x] != d[y]) ++res; 
			} else {
				fa[fx] = fy , d[fx] = (d[y] - d[x] + M) % M; 
			}
		}
		else {
			if(fx == fy) {
				if( ((d[x] - d[y] + M) % M) != 1 ) ++res;
			} else {
				fa[fx] = fy , d[fx] = (d[y] - d[x] + 1 + M) % M;
			}
		}
	}
	
	cout << res << '\n';
	
	return 0;
}

树上最大匹配( Vijos1892 , lgP1623)

voj被卡时限了 , lg被卡高精 , 思路应该是对的

\(sol\) :

\(dp[x][0 / 1]\) 表示以 \(x\) 为根节点的子树 , \(x\)有/没有被匹配的最大匹配数
$dp[x][0] = \sum_{ {y | x的子节点} } max\left(dp[y][0] , dp[y][1] \right) $
对于 \(dp[x][1]\) , 必须选择一个 \(dp[y][0]\) 与之转移 , 其他选 \(max{dp[y][0] , dp[y][1]}\)
这里我处理的思路是默认选先全部选择 \(max\left(dp[y][0] , dp[y][1] \right)\) , 再减去最小的代价

对于方案的计数 \(p[x][0 / 1]\) 依照 \(dp[x][0 / 1]\) 的转移路径来就行.
对于 \(p[x][0]\) , 每一个子树都是独立的 , 利用乘法原理相乘方案数 , 注意坑点\(dp[y][0] , dp[y][1]\)均是最优值 , 要将两者均考虑 (看代码中 \(sum\) 的作用)

对于 \(p[x][1]\) , 则有

\(p[x][1] = \sum_{ {y | x的最优转移点 } } p[y][0] * \prod_{k | k为x子节点且不等于 y} k节点的最优值下的方案数(可能两者均选)\)

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using ll = long long;

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	int n;
	std::cin >> n;
	std::vector<std::vector<int>> g(n);
	for(int i = 0 ; i < n - 1 ; ++i) {
		int x,y; std::cin >> x >> y;
		--x , --y;
		g[x].push_back(y);
		g[y].push_back(x);
	}
	
	ll mod;
	std::cin >> mod;
	
	std::vector<ll> res(n + 5);
	std::vector<std::vector<ll>> p(n , std::vector<ll>(2 , 0));
	std::vector<std::vector<ll>> dp(n , std::vector<ll>(2 , 0));
	
	std::function<void(int,int)> dfs = [&] (int x,int fa) {
		
		res[x] = 1E9 ;
		for(auto y : g[x]) {
			if(y == fa) continue;
			dfs(y , x); 
			dp[x][0] += std::max(dp[y][0] , dp[y][1]);
			if(dp[y][0] >= dp[y][1]) dp[x][1] += dp[y][0] , res[x] = 0; 
			else dp[x][1] += dp[y][1] , res[x] = std::min(res[x] , dp[y][1] - dp[y][0]);
		}
		if(res[x] != 1E9) dp[x][1] = dp[x][1] - res[x] + 1;
	};
	
	dfs(0 , - 1);
	
	std::function<void(int,int)> dfs1 = [&] (int x,int fa) {
		
		int sz = g[x].size();
		std::vector<ll> premul(sz + 5) , sufmul(sz + 5);
		
		int m = 0;
		p[x][0] = 1;
		for(auto y : g[x]) {
			
			if(y == fa) continue;
			dfs1(y , x) ;
			++m;
			
			ll sum = 0;
			int mx = std::max(dp[y][0] , dp[y][1]);
			if(dp[y][0] == mx) sum += p[y][0];
			if(dp[y][1] == mx) sum += p[y][1];
			sum %= mod;
			p[x][0] = p[x][0] * sum % mod;
			premul[m] = sufmul[m] = sum;	
			
		}
		
		premul[0] = sufmul[m + 1] = 1;
		for(int i = 2 ; i <= m ; ++i) {
			premul[i] = premul[i] * premul[i - 1] % mod;	
		}
		for(int i = m - 1 ; i >= 1; --i) {
			sufmul[i] = sufmul[i] * sufmul[i + 1] % mod;
		}
		
		m = 0;
		for(auto y : g[x]) {
			if(y == fa) continue; ++m;
			if(res[x] == 0) {
				if(dp[y][0] >= dp[y][1]) 
					p[x][1] = ( p[x][1] % mod + p[y][0] * premul[m - 1] % mod * sufmul[m + 1] % mod ) % mod;
			}
			else {
				if(dp[y][1] - dp[y][0] == res[x]) {
					p[x][1] = ( p[x][1] % mod + p[y][0] * premul[m - 1] % mod * sufmul[m + 1] % mod ) % mod;
				}
			}
		}
	};
	
	dfs1(0 , -1);
	
	ll sum = 0;
	ll mx = std::max(dp[0][0] , dp[0][1]);
	if(dp[0][0] == mx) sum = ( sum + p[0][0] ) % mod;
	if(dp[0][1] == mx) sum = ( sum + p[0][1] ) % mod;
	
	std::cout << mx << '\n' << sum << '\n';
	
	return 0;	
}

Acwing 375 Ants

线段不相交转换为最短距离 , 同时注意开始两者的值一定要开平方
因为证明不相交为最短距离就是利用三角形两边之和大于第三边证明的
不开方不一定有这个性质
\(a + b > c \nRightarrow a^2 + b^2 > c^2\)

#include <bits/stdc++.h>
using ll = long long;

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	int n;
	std::cin >> n;
	std::vector<std::array<int,2>> b(n),w(n);
	
	for(int i = 0 ; i < n ; ++i) {
		std::cin >> b[i][0] >> b[i][1];
	}
	for(int i = 0 ; i < n ; ++i) {
		std::cin >> w[i][0] >> w[i][1];
	}
	
	using D = double;
	auto get = [&](int x1,int y1,int x2,int y2) -> D {
		return sqrt( 1.0 * (x1 - x2) * (x1 - x2) + 1.0 * (y1 - y2) * (y1 - y2) );
	};
	
	std::vector<std::vector<D>> f(n , std::vector<D>(n));
	for(int i = 0 ; i < n ; ++i) {
		for(int j = 0 ; j < n ; ++j) {
			f[i][j] = get(w[i][0] , w[i][1] , b[j][0] , b[j][1]);
			f[i][j] = -f[i][j];
		}
	}
	
	std::vector<int> match(n , -1);
	std::vector<D> la(n),lb(n);
	std::vector<bool> va(n),vb(n);
	
	//预处理顶标 
	for(int i = 0 ; i < n ; ++i) {
		lb[i] = 0 , la[i] = -1E9;
		for(int j = 0 ; j < n ; ++j) {
			la[i] = std::max(la[i] , f[i][j]);
		}	
	}
	
	
	std::function<bool(int)> dfs = [&](int x) {
		va[x] = true;
		for(int y = 0 ; y < n ; ++y) {
			if(!vb[y] && fabs(la[x] + lb[y] - f[x][y]) < 1e-4) {
				vb[y] = true;
				if(match[y] == -1 || dfs(match[y])) {
					match[y] = x ; return true;
				}
			}	
		}
		return false;	
	};
	
	for (int i = 0 ; i < n ; ++i) { //正对于每一个左部图 i  
		while (true) {
			for(int j = 0 ; j < n ; ++j)
				va[j] = vb[j] = false;
			
			if(dfs(i)) break;
			
			D d = 1E9;
			for(int j = 0 ; j < n ; ++j) {
				if(va[j]) {
					for(int k = 0 ; k < n ; ++k) {
						if(!vb[k]) d = std::min(d , la[j] + lb[k] - f[j][k]);			
					}
				}
			}
			
			for(int j = 0 ; j < n ; ++j) {
				if(va[j]) la[j] -= d;
				if(vb[j]) lb[j] += d;
			} 
				
		}
	}
	
	for(int i = 0 ; i < n ; ++i)
		std::cout << match[i] + 1 << '\n';
		
	return 0;	
} 

HDU 1435 稳定婚姻系统

用 "接收" 去匹配 "发射" 才是对的 , 容量属于"接收"的匹配的择偶的条件

#include <bits/stdc++.h>
using ll = long long;

struct Node{
	int id,cap;
	double x,y,z;
};

void solve() 
{
	int n;
	std::cin >> n;

	std::vector<Node> tra(n + 1) , rec(n + 1);
	for (int i = 1 ; i <= n ; ++i) {
		std::cin >> tra[i].id >> tra[i].cap >> tra[i].x >> tra[i].y >> tra[i].z;
	}
	for (int i = 1 ; i <= n ; ++i) {
		std::cin >> rec[i].id >> rec[i].cap >> rec[i].x >> rec[i].y >> rec[i].z;
	}
	 
	using D = double;
	std::vector<std::vector<D>> dist(n + 1 , std::vector<D>(n + 1 , 0));
	
	auto sq = [&] (D x) {
		return x * x;	
	};
	auto get = [&] (const Node a,const Node b) {
		return sqrt( sq(a.x - b.x) + sq(a.y - b.y) + sq(a.z - b.z) );
	};
	
	for (int i = 1 ; i <= n ; ++i) {
		for (int j = 1 ; j <= n ; ++j) {
			dist[i][j] = get(tra[i] , rec[j]);
		}
	}
	
	std::vector<int> pos(n + 1 , 0);
	std::vector<std::vector<int>> g(n + 1);
	
	for (int j = 1 ; j <= n ; ++j) {
		for (int i = 1 ; i <= n ; ++i) {
			g[j].push_back(i); 
		}
		std::sort(g[j].begin() , g[j].end() , [&](int a , int b){
			return dist[a][j] < dist[b][j];
		});
	}
	
	std::queue<int> q;
	std::vector<int> match(n + 1 , 0);
	for (int j = 1 ; j <= n ; ++j) {
		q.push(j);
	}
	
	while (!q.empty()) {
		int u = q.front() ; q.pop();
		while (true) {
			int v = g[u][pos[u]++];
			if (!match[v]) {
				match[v] = u ; break; 
			} else {
				if ( (dist[v][u] < dist[v][match[v]]) 
				|| ( fabs(dist[v][u] - dist[v][match[v]]) < 1e-6 && rec[u].cap > rec[match[v]].cap) ) {
					q.push(match[v]);
					match[v] = u;
					break;
				}
			}
		}
	}
	
	for	(int i = 1 ; i <= n ; ++i) {
		std::cout << tra[i].id << ' ' << rec[match[i]].id << '\n';
	}
}

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	int t;
	std::cin >> t;
	
	while (t--) {
		solve();
	}
	
	return 0; 
}

算法理解

二分图最大匹配

vis[x] 的作用是判断 x 在这一轮匹配当中是否已经在增广路中 ,
如果 x 在增广路中 , 我们不必在再去访问


posted @ 2023-06-30 10:56  xqy2003  阅读(23)  评论(0)    收藏  举报