2018冬令营模拟测试赛(四)

2018冬令营模拟测试赛(四)

[Problem A]挑战NPC

试题描述

TAT

输入

见“试题描述

输出

见“试题描述

输入示例

见“试题描述

输出示例

见“试题描述

数据规模及约定

见“试题描述

题解

这题不知为何我暴力 dfs 搜了 (90) 分……

我们可以考虑最苛刻的条件,即 (m = n - 1)(p = 3)

这样的话就是一棵树,我们要从一个根节点出发,最终回到根节点的某一个儿子。

将树二分染色,令根为黑色。那么对于黑色节点的子树就先经过这个点然后往子树里面跳,最终回到某个儿子(白),然后跳到另一个儿子(白)的一个儿子(黑)中,对于那个黑点重复这种过程;对于白色节点的子树,从前面的过程可以看出我进入这个子树的时候,已经在某个儿子当中了,我的目标就是最后出来的时候踩在这个字数的白根上。这显然是可以实现的,对于黑点我们先输出路径再递归,白点先递归在输出即可。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)

int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
	return x * f;
}

#define maxn 1010
#define maxm 2010

int n, fa[maxn], col[maxn];
int findset(int x) { return x == fa[x] ? x : fa[x] = findset(fa[x]); }

int m, head[maxn], nxt[maxm], to[maxm];
void AddEdge(int a, int b) {
	to[++m] = b; nxt[m] = head[a]; head[a] = m;
	swap(a, b);
	to[++m] = b; nxt[m] = head[a]; head[a] = m;
	return ;
}

void biparate(int u, int pa, int c) {
	col[u] = c;
	for(int e = head[u]; e; e = nxt[e]) if(to[e] != pa) biparate(to[e], u, 3 - c);
	return ;
}

int Path[maxn], cnt;
void solve(int u, int pa) {
	if(col[u] == 1) Path[++cnt] = u;
	for(int e = head[u]; e; e = nxt[e]) if(to[e] != pa) solve(to[e], u);
	if(col[u] == 2) Path[++cnt] = u;
	return ;
}

int main() {
	n = read(); int m = read(); read();
	rep(i, 1, n) fa[i] = i;
	rep(i, 1, m) {
		int a = read(), b = read(), u = findset(a), v = findset(b);
		if(u != v) fa[v] = u, AddEdge(a, b);
	}
	
	biparate(1, 0, 1);
	solve(1, 0);
	
	rep(i, 1, n) printf("%d%c", Path[i], i < n ? ' ' : '
');
	
	return 0;
}

[Problem B]仙人掌

试题描述

T_T

Q_Q

输入

见“试题描述

输出

见“试题描述

输入示例

见“试题描述

输出示例

见“试题描述

数据规模及约定

见“试题描述

题解

本题全场最难,看了题解还是觉得很烧脑……

我们一问一问来。

有根树的情况,令 (f_i) 表示 (i) 个节点的有根树的异构体个数,直接转移的话需要枚举有多少个儿子,每个儿子的大小,显然是指数级的复杂度。

考虑换一种 dp 顺序。我们每次把大小为 (i) 的有根树的贡献加进去,即每次都更新一下整个 dp 数组 (f_{i+1} sim f_n)(f_1 sim f_i) 不可能再被更新了,因为它们撑不下一个大小为 (i) 的子树),在我 dp 进行到第 (i) 轮之前,我的所有子树大小都最多只有 (i-1) 那么大。那么所有 dp 值都缺少 (f_i) 的贡献,于是就给每个 dp 值加上有大小为 (i) 的子树的情况,我们需要枚举它们需要多少个大小为 (i) 的子树:(forall j in (i, n], f_j += sum_{t=1}^{lfloor frac{j}{i} floor} { C_t^{f_i + t - 1} cdot f'_{j-it} }) 注意式中的 (f') 表示的是加入大小为 (i) 的子树之前的 dp 值,在实现的过程中我们可以倒序枚举 (j),避免前面对后面的影响。注:(C_t^{f_i + t - 1}) 表示的是从 (f_i) 种物品中选择 (t) 个,每种物品可以选无限次的方案数,至于为什么是这个可以自行百度(提示:转化成插板问题)。

这一问 (30) 分:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <map>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)

int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
	return x * f;
}

#define maxn 3010
#define LL long long

int n, MOD, f[maxn], inv[maxn];

int C[maxn][maxn]; // C[i][j]: f[i] choose j, can choose many times.
int Pow(int a, int b) {
	int ans = 1, t = a;
	while(b) {
		if(b & 1) ans = (LL)ans * t % MOD;
		t = (LL)t * t % MOD; b >>= 1;
	}
	return ans;
}

int main() {
	n = read(); MOD = read();
	
	inv[1] = 1;
	rep(i, 2, n) inv[i] = (LL)(MOD - MOD / i) * inv[MOD%i] % MOD;
	f[1] = 1;
	rep(i, 1, n) {
		C[i][0] = 1;
		rep(t, 1, n / i) C[i][t] = (LL)C[i][t-1] * (f[i] + t - 1) % MOD * inv[t] % MOD;
		dwn(j, n, i + 1) rep(t, 1, j / i) {
			f[j] += (LL)C[i][t] * f[j-t*i] % MOD;
			if(f[j] >= MOD) f[j] -= MOD;
		}
	}
	printf("%d
", f[n]);
	
	return 0;
}

对于无根树,也是这样 dp,只是在根节点上面特判一下每个子树的大小不能超过 (lfloor frac{n}{2} floor),在上面贡献的时候限制一下就好了。

对于奇数的情况很容易(直接按照上述方法计算即可),因为不会有重复计数的问题,它重心唯一。

但对于偶数,由于是双重心我们可能会重复计算。我的方法是先限制每个子树的大小不超过 (frac{n}{2} - 1),然后在处理两棵 (frac{n}{2}) 的子树对顶的情况,就是在 (f_{frac{n}{2}}) 中选择两个(可重复),用这个方案加上之前算的 (f_n) 就好了。

这一问 (50) 分:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <map>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)

int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
	return x * f;
}

#define maxn 3010
#define LL long long

int n, MOD, f[maxn], inv[maxn];

int C[maxn][maxn]; // C[i][j]: f[i] choose j, can choose many times.
int Pow(int a, int b) {
	int ans = 1, t = a;
	while(b) {
		if(b & 1) ans = (LL)ans * t % MOD;
		t = (LL)t * t % MOD; b >>= 1;
	}
	return ans;
}

int main() {
	n = read(); MOD = read();
	
	inv[1] = 1;
	rep(i, 2, n) inv[i] = (LL)(MOD - MOD / i) * inv[MOD%i] % MOD;
	f[1] = 1;
	rep(i, 1, (n & 1) ? n / 2 : n / 2 - 1) {
		C[i][0] = 1;
		rep(t, 1, n / i) C[i][t] = (LL)C[i][t-1] * (f[i] + t - 1) % MOD * inv[t] % MOD;
		dwn(j, n, i + 1) rep(t, 1, j / i) {
			f[j] += (LL)C[i][t] * f[j-t*i] % MOD;
			if(f[j] >= MOD) f[j] -= MOD;
		}
	}
	if(n & 1) printf("%d
", f[n]);
	else printf("%lld
", (f[n] + (LL)f[n/2] * (f[n/2] + 1) / 2 % MOD) % MOD);
	
	return 0;
}

接下来就进入到最丧的仙人掌了。首先仙人掌可以转化成“圆方树”,然后“方点”不占体积,且只能连接“圆点”。

(f_i) 的定义不变,现在由于有环,那么方点的儿子就是有序的了,所以我们需要再维护一些值用来辅助计算。

(cac_{0, i}) 表示一个“方点”为根的,子树个数 (ge 1) 个,大小为 (i)(注意方点不算在“大小”中)的异构树的数目;

(cac_{1, i}) 表示一个“方点”为根的,子树个数 (ge 2) 个,大小为 (i) 的异构树的数目;(为什么要分开记呢?因为不能有重边,对于这个有根树,如果它只有一个“圆点”儿子,那么在加上父亲的“圆点”只有两个点构成一个环,就是重边了)。

以上两个我们都还没有考虑环的对称同构,所以会重复计数,下面这个就要考虑了。

(cir_i) 表示一个“方点”为根的,子树个数 (ge 2) 个,大小为 (i) 的异构树的数目(考虑对称同构)。

转移:(cac_{1, i} = sum_{j=1}^{n-1} {cac_{0, j} cdot f_{i-j}})(cac_{0, i} = cac_{1, i} + f_i)(cir_i = cac_{1, i} + sum_{j=1}^{lfloor frac{i}{2} floor} {cac_{0, j} cdot f_{i-2j}})

(ge 1) 的“方”根树强塞一个儿子就是 (ge 2) 的“方”根树,(ge 2) 的“方”根树补上只有一个子树的情况就是 (ge 1) 的“方”根树,对称同构就是 (frac{不对称情况+对称情况}{2}) 来去重。

这一问 (100) 分:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <map>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)

int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
	return x * f;
}

#define maxn 3010
#define LL long long

int n, MOD, f[maxn], cac[2][maxn], cir[maxn], inv[maxn];

int C[maxn][maxn]; // C[i][j]: f[i] choose j, can choose many times.
int Pow(int a, int b) {
	int ans = 1, t = a;
	while(b) {
		if(b & 1) ans = (LL)ans * t % MOD;
		t = (LL)t * t % MOD; b >>= 1;
	}
	return ans;
}

int main() {
	n = read(); MOD = read();
	
	inv[1] = 1;
	rep(i, 2, n) inv[i] = (LL)(MOD - MOD / i) * inv[MOD%i] % MOD;
	f[1] = 1;
	rep(i, 1, n) {
		rep(j, 1, i - 1) {
			cac[1][i] += (LL)cac[0][j] * f[i-j] % MOD;
			if(cac[1][i] >= MOD) cac[1][i] -= MOD;
		}
		cac[0][i] = (cac[1][i] + f[i]) % MOD;
		cir[i] = cac[1][i];
		rep(j, 1, i >> 1) {
			cir[i] += (LL)cac[0][j] * max(1, f[i-2*j]) % MOD;
			if(cir[i] >= MOD) cir[i] -= MOD;
		}
		cir[i] = (LL)cir[i] * inv[2] % MOD;
		
		C[i][0] = 1;
		rep(j, 1, n / i) C[i][j] = (LL)C[i][j-1] * (f[i] + cir[i] + j - 1) % MOD * inv[j] % MOD;
		dwn(j, n, i + 1) rep(t, 1, j / i) {
			f[j] += (LL)C[i][t] * f[j-i*t] % MOD;
			if(f[j] >= MOD) f[j] -= MOD;
		}
	}
	printf("%d
", f[n]);
	
	return 0;
}

[Problem C]森林

试题描述

TwT

QwQ

输入

见“试题描述

输出

见“试题描述

输入示例

见“试题描述

输出示例

见“试题描述

数据规模及约定

见“试题描述

题解

又是一道 LCT 维护 dp 信息的题。这题对于虚边连出去的点直接暴力开个 map 维护每个子树的最大深度以及每个最大深度有多少个节点就好了。然后在 splay 中维护到最浅点的信息,由于要换根需要翻转,所以需要再维护一个到最深点的信息。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <map>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)

int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
	return x * f;
}

#define maxn 400010

int ans;
struct LCT {
	int fa[maxn], ch[maxn][2], siz[maxn], Ld[maxn], Lc[maxn], Rd[maxn], Rc[maxn], S[maxn], top;
	bool rev[maxn];
	map <int, int> tot[maxn];
	
	bool isrt(int u) { return ch[fa[u]][0] != u && ch[fa[u]][1] != u; }
	void maintain(int u) {
		siz[u] = 1;
		map <int, int> :: iterator it = tot[u].end(); it--;
		int ls = ch[u][0] ? siz[ch[u][0]] : 0, rs = ch[u][1] ? siz[ch[u][1]] : 0;
		Ld[u] = it->first + ls; Rd[u] = it->first + rs; Lc[u] = Rc[u] = it->second;
		if(ch[u][0]) {
			siz[u] += siz[ch[u][0]];
			if(Ld[u] < Ld[ch[u][0]]) Ld[u] = Ld[ch[u][0]], Lc[u] = Lc[ch[u][0]];
			else if(Ld[u] == Ld[ch[u][0]]) Lc[u] += Lc[ch[u][0]];
			if(Rd[u] < Rd[ch[u][0]] + rs + 1) Rd[u] = Rd[ch[u][0]] + rs + 1, Rc[u] = Rc[ch[u][0]];
			else if(Rd[u] == Rd[ch[u][0]] + rs + 1) Rc[u] += Rc[ch[u][0]];
		}
		if(ch[u][1]) {
			siz[u] += siz[ch[u][1]];
			if(Ld[u] < Ld[ch[u][1]] + ls + 1) Ld[u] = Ld[ch[u][1]] + ls + 1, Lc[u] = Lc[ch[u][1]];
			else if(Ld[u] == Ld[ch[u][1]] + ls + 1) Lc[u] += Lc[ch[u][1]];
			if(Rd[u] < Rd[ch[u][1]]) Rd[u] = Rd[ch[u][1]], Rc[u] = Rc[ch[u][1]];
			else if(Rd[u] == Rd[ch[u][1]]) Rc[u] += Rc[ch[u][1]];
		}
		return ;
	}
	void pushdown(int u) {
		if(!rev[u]) return ;
		if(ch[u][0]) rev[ch[u][0]] ^= 1, swap(Ld[ch[u][0]], Rd[ch[u][0]]), swap(Lc[ch[u][0]], Rc[ch[u][0]]), swap(ch[ch[u][0]][0], ch[ch[u][0]][1]);
		if(ch[u][1]) rev[ch[u][1]] ^= 1, swap(Ld[ch[u][1]], Rd[ch[u][1]]), swap(Lc[ch[u][1]], Rc[ch[u][1]]), swap(ch[ch[u][1]][0], ch[ch[u][1]][1]);
		rev[u] = 0;
		return ;
	}
	void rotate(int u) {
		int y = fa[u], z = fa[y], l = 0, r = 1;
		if(!isrt(y)) ch[z][ch[z][1]==y] = u;
		if(ch[y][1] == u) swap(l, r);
		fa[u] = z; fa[y] = u; fa[ch[u][r]] = y;
		ch[y][l] = ch[u][r]; ch[u][r] = y;
		maintain(y);
		return ;
	}
	void splay(int u) {
		int t = u; S[++top] = u;
		while(!isrt(t)) S[++top] = fa[t], t = fa[t];
		while(top) pushdown(S[top--]);
		while(!isrt(u)) {
			int y = fa[u], z = fa[y];
			if(!isrt(y)) {
				if(ch[y][0] == u ^ ch[z][0] == y) rotate(u);
				else rotate(y);
			}
			rotate(u);
		}
		return maintain(u);
	}
	
	void AddTotal(int u, int son) {
		if(!son) return ;
		if(!tot[u].count(Ld[son] + 1)) tot[u][Ld[son]+1] = Lc[son];
		else tot[u][Ld[son]+1] += Lc[son];
		return ;
	}
	void DelTotal(int u, int son) {
		if(!son) return ;
		tot[u][Ld[son]+1] -= Lc[son];
		if(!tot[u][Ld[son]+1]) tot[u].erase(Ld[son] + 1);
		return ;
	}
	void access(int u) {
		splay(u); AddTotal(u, ch[u][1]); ch[u][1] = 0; maintain(u);
		while(fa[u]) {
			splay(fa[u]); AddTotal(fa[u], ch[fa[u]][1]);
			DelTotal(fa[u], u); ch[fa[u]][1] = u; maintain(fa[u]);
			splay(u);
		}
		return ;
	}
	void makeroot(int u) {
		access(u);
		ans -= Lc[u];
		rev[u] ^= 1; swap(Ld[u], Rd[u]); swap(Lc[u], Rc[u]); swap(ch[u][0], ch[u][1]);
		ans += Lc[u];
		return ;
	}
	void link(int a, int b) {
		makeroot(a); access(b);
		ans -= Lc[a] + Lc[b];
		fa[a] = b; AddTotal(b, a); maintain(b);
		ans += Lc[b];
		return ;
	}
	void cut(int a) {
		access(a);
		ans -= Lc[a];
		int b = ch[a][0]; fa[b] = ch[a][0] = 0; maintain(a);
		ans += Lc[a] + Lc[b];
		return ;
	}
} sol;

int n, q;

int main() {
	n = read(); q = read(); ans = n;
	rep(i, 1, n) sol.tot[i][0] = 1, sol.maintain(i);
	rep(i, 1, n) {
		int u = read();
		if(u) sol.link(i, u);
	}
	printf("%d
", ans);
	while(q--) {
		int op = read(), a = read(), b;
		if(op == 1) {
			b = read();
			sol.link(a, b);
		}
		if(op == 2) sol.cut(a);
		printf("%d
", ans);
	}
	
	return 0;
}
原文地址:https://www.cnblogs.com/xiao-ju-ruo-xjr/p/8124845.html