2024.8.23 模拟赛总结

A.dist

Statement:

给定一棵 \(n(n \le 10^6)\) 个节点带边权的树,定义 \(\mathrm{Min}(x, y)\)\((x, y)\) 路径上的边权最小值。求 \(\max_{r = 1}^n {\sum_{v \ne i} \mathrm{Min}(r, v)}\)

Solution:

经典套路题。

首先注意到一条路径上的只有最小值才会产生贡献,于是对于一个连通块,我们找到最小的那个边,并分成两个连通块 \(\mathrm{A, B}\),假如把根放在 \(\mathrm A\) 中,那么对于连通块 \(\mathrm B\) 到根的所有路径贡献都是这个最小边的边权,然后递归计算 \(\mathrm A\) 的贡献,对于放在 \(\mathrm B\) 中的情况一样做即可。但是这样的时间复杂度是 \(O(n^2)\) 的。

于是将删边改成加边,具体的,我们将边按边权从大到小排序,每次合并两个集合,用个并查集维护即可。时间复杂度 \(O(n \log n)\)

qwq
#include<bits/stdc++.h>
#pragma GCC optimize(3, "Ofast", "inline")
#define int long long
using namespace std;

const int N = 1e6 + 10;

int n, fa[N], siz[N], val[N];
struct Edge{
	int u, v, w;
}E[N];
bool cmp(struct Edge E1, struct Edge E2){
	return E1.w > E2.w;
}
int findfa(int x){return fa[x] = (fa[x] == x) ? x : findfa(fa[x]);}

void merge(int x, int y, int w){
	int fx = findfa(x), fy = findfa(y);
	if(fx == fy) return; if(siz[fx] < siz[fy]) swap(fx, fy); 
	val[fx] = max(val[fx] + siz[fy] * w, val[fy] + siz[fx] * w);
	fa[fy] = fx; siz[fx] += siz[fy];
}

signed main(){
//	freopen("dist3.in", "r", stdin);
	freopen("a.in", "r", stdin);
	freopen("a.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin >> n; for(int i = 1; i <= n; i++) siz[i] = 1, fa[i] = i;
	for(int i = 1; i < n; i++) cin >> E[i].u >> E[i].v >> E[i].w;
	sort(E + 1, E + n, cmp);
	for(int i = 1; i < n; i++) merge(E[i].u, E[i].v, E[i].w);
	cout << val[findfa(1)];
	
	return 0;
}

B.wolf

Statement:

\(n(1 \le n \le 3000)\) 间屋子,由 \(n − 1\) 条双向道路连接成了一个树形结构。其中,第 \(i\) 间屋子里居住了 \(1\) 个种族为 \(c_i\) 的狼人。现在你要召集一个连通块内的狼人。设 \(a_j\) 表示被召集的种族为 \(j\) 的狼人个数,若存在 \(2 a_k > \sum a_j\) ,则种族为 \(k\) 的狼人会造反。你想知道有多少个连通块会使某种狼人造反,答案对 \(998244353\) 取模。

Solution:

首先对判定进行转化:\(2a_k > \sum_{j = 1}^n a_j \Leftrightarrow a_k - \sum_{j \ne k} a_j > 0\)

所以可以枚举作为众数的种类然后做一遍做一遍树上背包,这样时间复杂度是 \(O(n^3)\)。然后注意到对于一个总共有 \(cnt_i\) 个狼人的种类 \(i\),背包上下限是 \(cnt_i\),由树上背包的时间复杂度做一次是 \(O(ncnt_i)\) 的,于是总复杂度是 \(O(n^2)\) 的了。

关于容量上限为 $k$ 的树上背包的复杂度证明 考虑合并两颗子树时交叉点的贡献,将节点顺序排列成一个有序序列,我们对一个点认为它只可以与距离小于等于 $k$ 的点配对。显然只会有 $nk$ 个对,于是时间复杂度为 $O(nk)$。
qwq
#include<bits/stdc++.h>
#pragma GCC optimize(3, "Ofast", "inline")
#define int long long 
using namespace std;

const int N = 3000 + 10, mod = 998244353;

int n, m, col[N], _col[N], f[N][2 * N], cnt0[N], cnt1[N], ans, tmp[2 * N];

struct edge{
	int v, next;
}edges[N << 1];
int head[N], idx;
int id(int val){if(val < 0) val += 2 * m + 1; return val;}
void ADD(int& x, int y){x = (x + y) % mod;}

void add_edge(int u, int v){
	edges[++idx] = {v, head[u]};
	head[u] = idx;
}
int mymax(int x, int y){return (x > y) ? x : y;}

void dfs(int u, int fa){
	cnt0[u] = (!col[u]); cnt1[u] = col[u]; //f[u][0] = 1;
	for(int i = -m; i <= m; i++) f[u][id(i)] = 0;
	f[u][(cnt0[u] ? id(-1) : 1)] = 1;
//	cout << u << " " << f[u][id(-1)] << " " << f[u][1] << "\n";
	for(int i = head[u]; i; i = edges[i].next){
		int v = edges[i].v; if(v == fa) continue;
		dfs(v, u);
		for(int i = mymax(-m, -cnt0[u]); i <= cnt1[u]; i++) tmp[id(i)] = f[u][id(i)];
		for(int i = mymax(-m, -cnt0[u]); i <= cnt1[u]; i++){
			for(int j = mymax(-m, -cnt0[v]); j <= cnt1[v]; j++){
				if(i + j >= -m && i + j <= m) ADD(f[u][id(i + j)], tmp[id(i)] * f[v][id(j)] % mod);
			}
		}
		cnt1[u] += cnt1[v]; cnt0[u] += cnt0[v];
	}
//	for(int i = mymax(-m, -cnt0[u]); i <= cnt1[u]; i++) cout << u << " " << i << " " << f[u][id(i)] << "\n";
	for(int i = 1; i <= cnt1[u]; i++) ADD(ans, f[u][i]);
}

signed main(){
//	freopen("wolf6.in", "r", stdin);
    freopen("b.in", "r", stdin);
    freopen("b.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0); 
	cin >> n; for(int i = 1; i <= n; i++) cin >> _col[i];
	for(int i = 1; i < n; i++){
		int x, y; cin >> x >> y;
		add_edge(x, y); add_edge(y, x);
	}
	for(int tcol = 1; tcol <= n; tcol++){
		m = 0;
		for(int i = 1; i <= n; i++) col[i] = (_col[i] == tcol), m += col[i];
		if(m) dfs(1, 0);
	}
	cout << ans;
	
	return 0;
} 

C.ancestor

Statement:

给定一棵有 \(n\) 个节点的树和 \(q\) 次查询。每次查询时有三个整数 \(k, m\)\(r\),接着是 \(k\) 个树的节点 \(a_1, a_2 ,...,a_k\) 。假设树以 \(r\) 为根。

我们希望将这 \(k\) 个给定的节点分成至多 \(m\) 组,使得满足以下条件:

  • 每个节点必须在恰好一个组中,每个组必须至少有一个节点。
  • 在任何组内,不应存在两个不同的节点,使得一个节点是另一个节点的祖先(直接或间接)。
    你需要输出每次查询的方案数,结果对 \(10^9 + 7\) 取模。

其中 \(n, q \le 2 \times 10^5, \sum k \le 2 \times 10^5, m \le 300\)

Solution:

注意到影响到一个点分组的节点只有他的父亲,定义影响点 \(x\) 分组,即点集中的它的父亲个数为 \(ex_x\)。于是我们可以设 \(f_{i, j}\) 为考虑前 \(i\) 个点,分 \(j\) 组的方案数。注意到这个影响条件有传递性,于是考虑经典做法,我们将影响到它的点放到它的前面,即按 \(ex\) 值对序列排序。于是可以有 \(f_{i, j} = f_{i - 1, j - 1} + f_{i - 1, j} \times (j - ex_i)\)。而 \(ex\) 值直接树剖即可。时间复杂度 \(O(n \log n)\)

qwq
#include<bits/stdc++.h>
#define int long long 
using namespace std;

const int N = 1e5 + 10, mod = 1e9 + 7, M = 300 + 10; 

struct edge{
	int v, next;
}edges[N << 1];
int head[N], idx;
int n, fa[N], dep[N], dfn[N], top[N], siz[N], son[N], dfc, a[N], ex[N];
int f[N][M];

void add_edge(int u, int v){
	edges[++idx] = {v, head[u]};
	head[u] = idx;
}
void dfs1(int u, int father){
	fa[u] = father; siz[u] = 1; dep[u] = dep[fa[u]] + 1;
	for(int i = head[u]; i; i = edges[i].next){
		int v = edges[i].v; if(v == fa[u]) continue;
		dfs1(v, u); siz[u] += siz[v]; 
		if(siz[son[u]] < siz[v]) son[u] = v;
	} 
}
void dfs2(int u, int tp){
	dfn[u] = ++dfc; top[u] = tp; //cout << u << " " << top[u] << " " << son[u] << "\n";
	if(son[u]) dfs2(son[u], tp);
	else return;
	for(int i = head[u]; i; i = edges[i].next){
		int v = edges[i].v;
		if(v != fa[u] && v != son[u]) dfs2(v, v);
	}
}
struct BIT{
	int t[N];
	int lowbit(int x){return x & -x;}
	void add(int x, int k){for(; x <= n; x += lowbit(x)) t[x] += k;}
	int sum(int x){
		int ret = 0;
		for(; x; x -= lowbit(x)) ret += t[x];
		return ret;
	}
	int qry(int l, int r){return sum(r) - sum(l - 1);}
}tr;

bool cmp(int x, int y){return ex[x] < ex[y];}

int listqry(int x, int y){
	int ret = 0; //cout << x << " " << y << "\n";
	while(top[x] != top[y]){
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
//		cout << x << " " << top[x] << " " << dfn[x] << "\n";
		ret += tr.qry(dfn[top[x]], dfn[x]);
		x = fa[top[x]]; 
	}
	if(dfn[x] > dfn[y]) swap(x, y);
	ret += tr.qry(dfn[x], dfn[y]);
	return ret;
}

void solve(){
	int k, m, r; cin >> k >> m >> r; 
	for(int i = 1; i <= k; i++) cin >> a[i], tr.add(dfn[a[i]], 1);
	for(int i = 1; i <= k; i++) ex[a[i]] = listqry(r, a[i]);
	sort(a + 1, a + k + 1, cmp); f[0][0] = 1;
	for(int i = 1; i <= k; i++) ex[a[i]]--;//, cout << a[i] << " " << ex[a[i]] << "\n";
	for(int i = 1; i <= k; i++){
//		cout << i << " " << 0 << " " << f[i][0] << "\n";
		for(int j = 1; j <= m; j++){
			f[i][j] = f[i - 1][j - 1];
			if(j - ex[a[i]] > 0) f[i][j] = (f[i][j] + f[i - 1][j] * (j - ex[a[i]]) % mod) % mod;
//			cout << i << " " << j << " " << f[i][j] << "\n";
		}
//		cout << dfn[a[i]] << "\n"; 
	}
	int ans = 0;
	for(int i = 1; i <= m; i++) ans = (ans + f[k][i]) % mod;
	for(int i = 1; i <= k; i++) tr.add(dfn[a[i]], -1);
	cout << ans << "\n";
}

signed main(){
    freopen("c.in", "r", stdin);
    freopen("c.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int Q; cin >> n >> Q;
	for(int i = 1; i < n; i++){
		int x, y; cin >> x >> y;
		add_edge(x, y); add_edge(y, x);
	}
	dfs1(1, 0); dfs2(1, 1);
//	for(int i = 1; i <= n; i++) cout << dep[i] << " " << fa[i] << " " << dfn[i] << " " << son[i] << " " << top[i] << " " << siz[i] << "\n";
	while(Q--) solve();
	
	return 0;
} 

D.chess

Statement:

\(\rm A\)\(\rm B\) 正在进行一个游戏。这个游戏在 \(N\) 个顶点的图上进行。顶点编号为 \(1,2,...,N\)。图中有 \(N − 1\) 条红边和 \(N − 1\) 条蓝边,二者都构成一棵树。红边用 \((a_i, b_i)\) 表示,蓝边用 \((c_i, d_i)\) 表示。两人各有一个棋子,\(\rm A\) 的初始位置在顶点 \(\rm X\)\(\rm B\) 的初始位置在顶点 \(\rm Y\)

游戏采取轮流制,依次进行 \(1,2,3,...\) 的轮次。在奇数轮 \((1,3,5,...)\) 时,\(\rm A\) 可以选择移动到与其当前所在树 \(\rm a\) 中点相邻的节点,或者选择不动;在偶数轮 \((2,4,6,...)\) 时,\(\rm B\) 进行选择与其当前所在树 \(\rm b\) 中点相邻的节点,或者选择不动。当两人到达编号相同的点时,游戏结束。\(\rm A\) 希望尽可能地最大化游戏轮数,而 \(\rm B\) 则希望尽可能地最小化游戏轮数。在假定两者都按照最优策略行动的情况下,判断游戏能否结束,并在能结束的情况下求出总轮数。

其中 \(N \le 200000\)

Solution:

这道题实际上就是 \(\rm B\)\(\rm A\) 的过程,而 \(\rm A\) 可以不断的瞬移。首先预处理出每个点从 \(\rm A,B\) 的到达的步数 \(distA, distB\)。显然 \(\forall x, distA_x \le distB_x\)\(x\)\(\rm A\) 和他在 \(\rm A\) 子树中的点显然是不能去的。接着考虑其他点,观察到假如有一条 \(\rm A\) 中的边 \((x_i, y_i)\),且在 \(\rm B\)\((x_i, y_i)\) 的距离大于 \(2\)\(\rm A\) 就可以遛狗了,这样就无法结束了。

对于接下来的情况,显然 \(\rm A\) 不可能跳出 \(\rm B\) 的子树,于是 \(\rm A\) 就只能跑到 \(A\) 能到达中最深的点然后等死了。距离可以直接分类讨论,不用求 \(\rm LCA\),时间复杂度 \(O(n)\)

qwq
#include<bits/stdc++.h>
#define int long long 
using namespace std;

const int N = 2e5 + 10;

vector<int> B[N], R[N];
int n, dep[N], _fa[N], _upd3[N], ans, X, Y;

struct Edge{
	int x, y;
}E[N];

void dfsB(int u, int fa){
	_fa[u] = fa;
	for(int i = 0; i < B[u].size(); i++){
		int v = B[u][i]; if(v == fa) continue;
		dep[v] = dep[u] + 1; dfsB(v, u); 
	}
}
bool upd3(int x, int y){
	if(dep[x] < dep[y]) swap(x, y);
	if(dep[x] == dep[y] && _fa[x] != _fa[y]) return 1;
	if(dep[x] == dep[y] + 1 && _fa[x] != y) return 1;
	if(dep[x] == dep[y] + 2 && _fa[_fa[x]] != y) return 1;
	return (dep[x] - dep[y] > 2);
}
bool dfsR(int u, int fa, int _dep){
	if(_dep >= dep[u]) return false;
	if(_upd3[u]){cout << -1; return true;} ans = max(ans, dep[u] * 2);
	for(int i = 0; i < R[u].size(); i++){
		int v = R[u][i];
		if(v != fa){
			if(dfsR(v, u, _dep + 1)) return true; 	
		}
	}
	return false;
}

void solve(){
	cin >> n >> X >> Y; ans = 0;
	for(int i = 1; i <= n; i++) R[i].clear(), B[i].clear(), _upd3[i] = dep[i] = _fa[i] = 0;
	for(int i = 1; i < n; i++){
		int x, y; cin >> x >> y; E[i] = {x, y};
		R[x].push_back(y); R[y].push_back(x); 
	}
	for(int i = 1; i < n; i++){
		int x, y; cin >> x >> y;
		B[x].push_back(y); B[y].push_back(x);
	}
	dfsB(Y, 0);
	for(int i = 1; i < n; i++) if(upd3(E[i].x, E[i].y)) _upd3[E[i].x] = _upd3[E[i].y] = 1;
	if(!dfsR(X, 0, 0)) cout << ans; cout << "\n";
}

signed main(){
    freopen("d.in", "r", stdin);
    freopen("d.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int T; cin >> T; while(T--) solve();
	
	return 0;
}
posted @ 2024-08-23 08:34  Little_corn  阅读(12)  评论(0编辑  收藏  举报