【集训队作业】IOI 2020 集训队作业 试题泛做 6

Atcoder Regular Contest 97

Problem F. Monochrome Cat

若原树中存在一个黑色的叶子,那么我们可以将其删去而不影响答案。

重复这个过程,我们会得到一棵仅包含白色叶子的树,显然,这棵树中所有的节点都会被访问至少一次。

対该树进行树形 DP ,记:

$ dp_{i,0} $ 表示从 $ i $ 号点的父亲出发,完成对 $ i $ 子树的访问后回到 $ i $ 号点的父亲的最小用时。

$ dp_{i,1} $ 表示从 $ i $ 号点的父亲出发,完成对 $ i $ 子树的访问后停在 $ i $ 号点的子树内部的最小用时。

$ dp_{i,2} $ 表示从 $ i $ 号点出发,完成对 $ i $ 号点父亲所在的 $ i $ 号点的子树的访问后回到 $ i $ 号点的最小用时。

获取答案时枚举路径两端的 LCA ,通过上述 DP 结果更新答案。

时间复杂度 $ O(N) $ 。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const long long INF = 1e18;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
vector <int> a[MAXN], b[MAXN];
int n, d[MAXN]; char col[MAXN];
long long dp[MAXN][3], ans;
bool vis[MAXN];
void work(int pos, int fa) {
	for (unsigned i = 0; i < a[pos].size(); i++)
		if (fa != a[pos][i] && !vis[a[pos][i]]) work(a[pos][i], pos);
	bool now = col[pos] == 'B';
	long long tans = 2;
	for (unsigned i = 0; i < a[pos].size(); i++)
		if (fa != a[pos][i] && !vis[a[pos][i]]) {
			tans += dp[a[pos][i]][0];
			now ^= true;
		}
	dp[pos][0] = tans + now;
	dp[pos][1] = tans + now - 1;
	for (unsigned i = 0; i < a[pos].size(); i++)
		if (fa != a[pos][i] && !vis[a[pos][i]]) {
			chkmin(dp[pos][1], tans + !now - 1 - dp[a[pos][i]][0] + dp[a[pos][i]][1]);
		}
}
void getans(int pos, int fa) {
	if (fa == 0) {
		bool now = col[pos] == 'W'; int cnt = 0;
		long long tans = 0;
		for (unsigned i = 0; i < a[pos].size(); i++)
			if (fa != a[pos][i] && !vis[a[pos][i]]) {
				tans += dp[a[pos][i]][0];
				now ^= true;
				cnt++;
			}
		for (unsigned i = 0; i < a[pos].size(); i++)
			if (fa != a[pos][i] && !vis[a[pos][i]]) {
				dp[a[pos][i]][2] = dp[pos][0] - dp[a[pos][i]][0] - !now + now;
				getans(a[pos][i], pos);
			}
		if (cnt == 1) {
			for (unsigned i = 0; i < a[pos].size(); i++)
			if (fa != a[pos][i] && !vis[a[pos][i]]) {
				chkmin(ans, dp[a[pos][i]][1] + 1);
				return;
			}
		}
		chkmin(ans, tans + now);
		long long Max = -1e18, Nax = -1e18;
		for (unsigned i = 0; i < a[pos].size(); i++)
			if (fa != a[pos][i] && !vis[a[pos][i]]) {
				long long tmp = dp[a[pos][i]][0] - dp[a[pos][i]][1];
				if (tmp > Max) {
					Nax = Max;
					Max = tmp;
				} else chkmax(Nax, tmp);
			}
		chkmin(ans, 1 + tans + !now - Max - Nax);
	} else {
		for (unsigned i = 0; i < a[pos].size(); i++)
			if (fa != a[pos][i] && !vis[a[pos][i]]) {
				dp[a[pos][i]][2] = dp[pos][0] - dp[a[pos][i]][0] + dp[pos][2];
				getans(a[pos][i], pos);
			}
		bool now = col[pos] == 'B';
		long long tans = dp[pos][2];
		for (unsigned i = 0; i < a[pos].size(); i++)
			if (fa != a[pos][i] && !vis[a[pos][i]]) {
				tans += dp[a[pos][i]][0];
				now ^= true;
			}
		chkmin(ans, tans + now);
		long long Max = -1e18, Nax = -1e18;
		for (unsigned i = 0; i < a[pos].size(); i++)
			if (fa != a[pos][i] && !vis[a[pos][i]]) {
				long long tmp = dp[a[pos][i]][0] - dp[a[pos][i]][1];
				if (tmp > Max) {
					Nax = Max;
					Max = tmp;
				} else chkmax(Nax, tmp);
			}
		chkmin(ans, 1 + tans + !now - Max - Nax);
	}
}
int main() {
	read(n);
	for (int i = 1; i <= n - 1; i++) {
		int x, y; read(x), read(y);
		d[x]++, d[y]++;
		a[x].push_back(y);
		a[y].push_back(x);
	}
	scanf("%s", col + 1);
	static int q[MAXN];
	int l = 0, r = -1;
	for (int i = 1; i <= n; i++)
		if (d[i] <= 1) q[++r] = i;
	while (l <= r) {
		int tmp = q[l++];
		if (col[tmp] == 'B') {
			vis[tmp] = true;
			for (unsigned i = 0; i < a[tmp].size(); i++)
				if (--d[a[tmp][i]] == 1) q[++r] = a[tmp][i];
		}
	}
	for (int i = 1; i <= n; i++)
		if (!vis[i]) {
			work(i, 0);
			ans = INF;
			getans(i, 0);
			writeln(ans);
			return 0;
		}
	printf("0\n");
	return 0;
}

Atcoder Regular Contest 98

Problem F. Donation

若我们经过了一个点多次,那么我们不必在最后一次经过该点之前对其进行捐献,而可以等到最后一次经过它时再进行捐献。

因此,令 $ C_i=max{A_i-B_i,0} $ ,如果我们默认只有最后一次经过一个点时才会对其进行捐献,对 $ A_i $ 的限制可以等价地表示为在任何时刻,一个处于 $ i $ 点的人必须持有 $ C_i $ 单位的货币。

考虑 $ C_x $ 最大的点 $ x $ ,对 $ x $ 进行捐献后,我们将不再经过 $ x $ ,也就是说,我们会进入删去 $ x $ 所分割出的一个连通分量中,不再出来,不妨设该连通分量为 $ G $ 。

扫描二维码关注公众号,回复: 10184569 查看本文章

我们发现,在对 $ x $ 进行捐献之前对 $ G $ 内部的点进行的捐献可以推迟到对 $ x $ 进行捐献之后,因此我们不失一般性地认为我们只会先对删去 $ x $ 所分割出的其他连通分量捐献,然后对 $ x $ 捐献,最后对 $ G $ 捐献。

我们建出如下树形结构:将当前连通分量内 $ C_i $ 最大的点作为根,并将其在原图中删去,对于新产生的各个连通分量分别建树,将它们的根部连向当前联通分量的根部。

这棵树可以通过按 $ C_i $ 从小到大考虑所有的点在 $ O(NLogN) $ 的时间内构建出来。

然后记 $ dp_i $ 表示完成对当前连通分量的捐献需要具有的最少资金数, $ sum_i $ 表示完成对当前连通分量的捐献将会花费的最少资金数,简单树形 DP 即可。

时间复杂度 $ O(NLogN) $ 。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, root, a[MAXN], b[MAXN], f[MAXN], pos[MAXN], fa[MAXN];
vector <int> e[MAXN], t[MAXN];
long long dp[MAXN], sum[MAXN];
void work(int pos) {
	if (t[pos].size() == 0) {
		dp[pos] = a[pos] + b[pos];
		sum[pos] = b[pos];
		return;
	}
	sum[pos] = b[pos];
	for (unsigned i = 0; i < t[pos].size(); i++) {
		work(t[pos][i]);
		sum[pos] += sum[t[pos][i]];
	}
	dp[pos] = 1e18;
	for (unsigned i = 0; i < t[pos].size(); i++)
		chkmin(dp[pos], max(dp[t[pos][i]], 1ll * a[pos]) + sum[pos] - sum[t[pos][i]]);
}
bool cmp(int x, int y) {
	return a[x] < a[y];
}
int F(int x) {
	if (f[x] == x) return x;
	else return f[x] = F(f[x]);
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++) {
		read(a[i]), read(b[i]);
		a[i] = max(a[i] - b[i], 0);
		pos[i] = i;
	}
	sort(pos + 1, pos + n + 1, cmp);
	for (int i = 1; i <= m; i++) {
		int x, y;
		read(x), read(y);
		e[x].push_back(y);
		e[y].push_back(x);
	}
	memset(fa, -1, sizeof(fa));
	for (int i = 1; i <= n; i++) {
		int tmp = pos[i];
		f[tmp] = tmp;
		for (unsigned j = 0; j < e[tmp].size(); j++)
			if (fa[F(e[tmp][j])] == 0) fa[F(e[tmp][j])] = tmp, f[F(e[tmp][j])] = tmp;
		fa[tmp] = 0;
	}
	for (int i = 1; i <= n; i++)
		if (fa[i]) t[fa[i]].push_back(i);
		else root = i;
	work(root);
	writeln(dp[root]);
	return 0;
}

Atcoder Regular Contest 99

Problem F. Eating Symbols Hard

考虑对一个方案进行哈希:令一个方案的哈希值为 $ Hash(S)=\sum_{i=-\infty}{+\infty}A_iXi\ Mod\ M $ ,其中 $ X $ 为一个随机数, $ M $ 为一个大质数。该哈希函数有以下性质:
1 、 $ Hash(+S)=Hash(S)+1 $
2 、 $ Hash(-S)=Hash(S)-1 $
3 、 $ Hash(>S)=Hash(S)*X $
4 、 $ Hash(<S)=Hash(S)*X^{-1} $

定义函数 $ pls(Hash(S)) $ 为在 $ S $ 前添加一个 $ + $ 后得到的字符串的哈希值。

显然有 $ pls(Hash(S))=Hash(S)+1 $ ,也即 $ Hash(S)=pls(Hash(S))-1 $ 。

定义 $ pls^{-1}(Hash(S)) $ 为在 $ S $ 前删除一个 $ + $ 后得到的字符串的哈希值,称为上述函数的逆函数,有 $ pls^{-1}(Hash(S))=Hash(S)-1 $ 。

类似的,我们可以对剩余的三个操作也定义以上函数。

令 $ f_{r,l}(x) $ 表示用 $ S_r $ 到 $ S_l $ 的字符对应的函数依次操作 $ x $ 得到的结果。

令 $ f^{-1}_{r,l}(x) $ 表示用 $ S_r $ 到 $ S_l $ 的字符对应的逆函数依次操作 $ x $ 得到的结果。

令 $ c=f_{n,1}(0) $ ,我们希望统计 $ f_{j,i}(0)=c $ 的 $ (i,j)(i≤j) $ 的数量。

$ f_{j,i}(0)=c\Leftrightarrow f{-1}_{i,N}(f_{j,i}(0))=f{-1}{i,N}©\Leftrightarrow f{-1}_{j+1,N}(0)=f{-1}{i,N}© $

对于所有 $ i $ ,分别计算 $ a_i=f{-1}_{i,N}©,b_i=f{-1}_{i+1,N}(0) $ ,然后统计 $ a_i=b_j(i≤j) $ 的 $ (i,j) $ 数量即可。

注意到所有函数和逆函数均为一次函数,因此它们嵌套起来同样是一次函数,维护一次函数即可计算 $ a_i $ 和 $ b_i $ 。

取 $ Cnt $ 个不同的 $ X $ ,减小哈希冲突的概率。

时间复杂度 $ O(NLogN\times Cnt) $ ,取 $ Cnt=6 $ 。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 250005;
const int P = 1e9 + 7;
const int val[6] = {18, 40, 23, 2333333, 58, 720};
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
struct Hash {int val[6]; };
struct info {Hash k, b; };
bool operator < (Hash a, Hash b) {
	for (int i = 0; i <= 5; i++)
		if (a.val[i] != b.val[i]) return a.val[i] < b.val[i];
	return false;
}
map <Hash, int> mp;
char s[MAXN];
int inv[6];
Hash unit() {
	Hash ans;
	memset(ans.val, 0, sizeof(ans.val));
	return ans;
}
info unitinfo() {
	info ans;
	for (int i = 0; i <= 5; i++) {
		ans.b.val[i] = 0;
		ans.k.val[i] = 1;
	}
	return ans;
}
Hash operator * (Hash x, info a) {
	Hash ans;
	for (int i = 0; i <= 5; i++)
		ans.val[i] = (1ll * a.k.val[i] * x.val[i] + a.b.val[i]) % P;
	return ans;
}
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
void pls(Hash &tmp) {
	for (int i = 0; i <= 5; i++)
		tmp.val[i] = (tmp.val[i] + 1) % P;
}
void mns(Hash &tmp) {
	for (int i = 0; i <= 5; i++)
		tmp.val[i] = (tmp.val[i] + P - 1) % P;
}
void rit(Hash &tmp) {
	for (int i = 0; i <= 5; i++)
		tmp.val[i] = (1ll * tmp.val[i] * val[i]) % P;
}
void lft(Hash &tmp) {
	for (int i = 0; i <= 5; i++)
		tmp.val[i] = (1ll * tmp.val[i] * inv[i]) % P;
}
void pls(info &tmp) {
	for (int i = 0; i <= 5; i++)
		tmp.b.val[i] = (tmp.b.val[i] + tmp.k.val[i]) % P;
}
void mns(info &tmp) {
	for (int i = 0; i <= 5; i++)
		tmp.b.val[i] = (tmp.b.val[i] + P - tmp.k.val[i]) % P;
}
void rit(info &tmp) {
	for (int i = 0; i <= 5; i++)
		tmp.k.val[i] = (1ll * tmp.k.val[i] * val[i]) % P;
}
void lft(info &tmp) {
	for (int i = 0; i <= 5; i++)
		tmp.k.val[i] = (1ll * tmp.k.val[i] * inv[i]) % P;
}
int main() {
	for (int i = 0; i <= 5; i++)
		inv[i] = power(val[i], P - 2);
	int n; read(n);
	scanf("\n%s", s + 1);
	Hash now = unit();
	for (int i = n; i >= 1; i--) {
		if (s[i] == '+') pls(now);
		if (s[i] == '-') mns(now);
		if (s[i] == '>') rit(now);
		if (s[i] == '<') lft(now);
	}
	Hash tmp = unit(); mp[tmp]++;
	info Now = unitinfo(), Tmp = unitinfo();
	long long ans = 0;
	for (int i = n; i >= 1; i--) {
		if (s[i] == '-') pls(Now), pls(Tmp);
		if (s[i] == '+') mns(Now), mns(Tmp);
		if (s[i] == '<') rit(Now), rit(Tmp);
		if (s[i] == '>') lft(Now), lft(Tmp);
		ans += mp[now * Now];
		mp[tmp * Tmp]++;
	}
	writeln(ans);
	return 0;
}

Atcoder Regular Contest 100

Problem F. Colorful Sequences

考虑一个子问题,如何求解 Colorful 的序列个数。

可以通过 DP 完成,记 d p i , j dp_{i,j} 表示长度为 i i 的, Colorful 程度为 j j 的序列个数,其中 Colorful 程度 j j 被定义为最大的 j j ,使得序列最后 j j 个元素互不相同,特别地,序列若已经是 Colorful 的,其 Colorful 程度为 K K 。显然有 O ( K ) O(K) 转移,并且可以用前缀和优化至均摊 O ( 1 ) O(1) ,因此,解决该子问题的时间复杂度为 O ( N K ) O(NK)

考虑原问题,枚举子串出现的位置,计算在剩余部分填入数字,使得得到的序列 Colorful 的方案数,求和即为答案。

分以下三种情况考虑:

( 1 ) (1) 、给定序列是 Colorful 的,显然,答案为 ( N M + 1 ) × K N M (N-M+1)\times K^{N-M}

( 2 ) (2) 、给定序列包含相同的元素,那么,判断序列 Colorful 的区间不可能横跨出现位置的两侧,可以分为左右两个独立的子问题,用与上文相同的方式解决。

( 3 ) (3) 、给定序列中的元素两两不同,此时,序列本身是什么已经不重要了。可以先用 ( 2 ) (2) 中的计算方式计算,剩余没有被计算的部分即为判断序列 Colorful 的区间必须横跨出现位置的两侧的情况,枚举判断序列 Colorful 的区间最早出现的位置,则两侧同样可以分为左右两个独立的子问题,可以通过将上文 DP 中的边反向完成。

时间复杂度 O ( N K ) O(NK)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 25005;
const int MAXK = 405;
const int P = 1e9 + 7;
typedef long long ll;
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int n, k, m, a[MAXN];
int solveb() {
	static int f[MAXN][MAXK], g[MAXN][MAXK], cnt[MAXK];
	int l = m, r = m;
	memset(cnt, 0, sizeof(cnt));
	for (int i = 1; i <= m; i++)
		if (++cnt[a[i]] == 2) {
			l = i - 1;
			break;
		}
	memset(cnt, 0, sizeof(cnt));
	for (int i = m; i >= 1; i--)
		if (++cnt[a[i]] == 2) {
			r = m - i;
			break;
		}
	f[0][l] = g[0][r] = 1;
	for (int i = 1; i <= n; i++) {
		int sf = 0, sg = 0;
		for (int j = k; j >= 1; j--) {
			if (j != k) update(sf, f[i - 1][j]);
			if (j != k) update(sg, g[i - 1][j]);
			f[i][j] = sf, update(f[i][j], 1ll * f[i - 1][j - 1] * (k - j + 1) % P);
			g[i][j] = sg, update(g[i][j], 1ll * g[i - 1][j - 1] * (k - j + 1) % P);
		}
		update(f[i][k], 1ll * f[i - 1][k]* k % P);
		update(g[i][k], 1ll * g[i - 1][k]* k % P);
	}
	int ans = 0;
	for (int l = 0, r = n - m; r >= 0; l++, r--) {
		int sf = 0, sg = 0;
		for (int i = 1; i <= k - 1; i++) {
			update(sf, f[l][i]);
			update(sg, g[r][i]);
		}
		update(ans, 1ll * sf * g[r][k] % P);
		update(ans, 1ll * sg * f[l][k] % P);
		update(ans, 1ll * f[l][k] * g[r][k] % P);
	}
	return ans;
}
int solveu() {
	static int f[MAXN][MAXK];
	for (int i = 1; i <= k - 1; i++)
		f[0][i] = 1;
	for (int i = 1; i <= n; i++) {
		int sf = 0;
		for (int j = 1; j <= k - 1; j++) {
			update(sf, f[i - 1][j]), f[i][j] = sf;
			update(f[i][j], 1ll * f[i - 1][j + 1] * (k - j) % P);
		}
		f[i][k] = 1ll * f[i - 1][k] * k % P;
	}
	int ans = solveb(), fac = 1;
	for (int i = 1; i <= k - m; i++)
		fac = 1ll * fac * i % P;
	for (int l = 0, r = n - m; r >= 0; l++, r--)
	for (int i = 1, j = k - m - i; j >= 1; i++, j--)
		if (i <= l && j <= r) update(ans, 1ll * fac * f[l - i][k - 1] % P * f[r - j][j + m] % P);
	return ans;
}
bool Unique() {
	static int cnt[MAXK];
	for (int i = 1; i <= m; i++)
		cnt[a[i]]++;
	for (int i = 1; i <= k; i++)
		if (cnt[i] >= 2) return false;
	return true;
}
bool contain() {
	static int cnt[MAXK], l = 1;
	for (int i = 1; i <= m; i++) {
		if (++cnt[a[i]] == 2) {
			while (cnt[a[i]] == 2) cnt[a[l++]]--;
		}
		if (i - l + 1 == k) return true;
	}
	return false;
}
int main() {
	read(n), read(k), read(m);
	for (int i = 1; i <= m; i++)
		read(a[i]);
	if (contain()) writeln(1ll * (n - m + 1) * power(k, n - m) % P);
	else if (Unique()) writeln(solveu());
	else writeln(solveb());
	return 0;
}

Atcoder Regular Contest 101

Problem E. Ribbons on Tree

不考虑所有边都被覆盖的限制,记 f i f_i 表示 i i 的点的树的匹配方案数,显然有
f i = { 1 i = 0 0 i 1   ( m o d   2 ) ( i 1 ) × f i 2 i 0   ( m o d   2 ) f_i=\left\{\begin{array}{rcl}1 && {i=0}\\0 && {i\equiv 1\ (mod\ 2)}\\(i-1)\times f_{i-2} && {i\equiv 0\ (mod\ 2)}\end{array} \right.

考虑对覆盖的条件进行容斥,枚举一个边集 S S ,强制其不被覆盖,计算方案数,并乘上系数 ( 1 ) S (-1)^{|S|} 计入答案。

则显然可以用树上背包优化。

时间复杂度 O ( N 2 ) O(N^2)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
const int P = 1e9 + 7;
typedef long long ll;
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int dp[MAXN][MAXN];
int size[MAXN], f[MAXN];
vector <int> a[MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
void work(int pos, int fa) {
	dp[pos][1] = 1, size[pos] = 1;
	for (auto x : a[pos])
		if (x != fa) {
			work(x, pos);
			static int tmp[MAXN];
			memset(tmp, 0, sizeof(tmp));
			for (int i = 0; i <= size[pos]; i++)
			for (int j = 0; j <= size[x]; j++)
				update(tmp[i + j], 1ll * dp[pos][i] * dp[x][j] % P);
			memcpy(dp[pos], tmp, sizeof(tmp));
			size[pos] += size[x];
		}
	for (int i = 1; i <= size[pos]; i++)
		update(dp[pos][0], P - 1ll * dp[pos][i] * f[i] % P);
}
int main() {
	int n; read(n), f[0] = 1;
	for (int i = 2; i <= n; i += 2)
		f[i] = (i - 1ll) * f[i - 2] % P;
	for (int i = 1; i <= n - 1; i++) {
		int x, y; read(x), read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	work(1, 0);
	int ans = 0;
	for (int i = 1; i <= n; i++)
		update(ans, 1ll * dp[1][i] * f[i] % P);
	cout << ans << endl;
	return 0;
}

Problem F. Robots and Exits

显然,各个机器人只能走到两侧的出口,输入可以等价地描述为数组 ( a i , b i ) (a_i,b_i) ,分别表示第 i i 个机器人左右两侧第一个出口的距离。

考虑对二元组 ( a i , b i ) (a_i,b_i) 排序,并去重,选择一个子集 S S ,判断是否可以使得 S S 中所有机器人走到左侧出口,其余机器人走到右侧出口。

S S 需要满足如下两点:
( 1 ) (1) 、若 a i = a i + 1 a_i=a_{i+1} ,且 i S i\in S ,则 i + 1 S i+1\in S
( 2 ) (2) 、若 i < j , b i b j i<j,b_i\geq b_j ,且 i S i\notin S ,则 j S j\notin S

由此可以设计 DP ,记 d p i dp_i 表示考虑所有 a x a i a_x\leq a_i 的位置 x x ,不在 S S 中的元素最大为 b i b_i 的方案数,显然有平方转移,且可以用树状数组简单优化。

时间复杂度 O ( N L o g N ) O(NLogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int P = 1e9 + 7; 
typedef long long ll;
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
struct BinaryIndexTree {
	int n, a[MAXN];
	void init(int x) {
		n = x;
		memset(a, 0, sizeof(a));
	}
	void modify(int x, int d) {
		for (int i = x; i <= n; i += i & -i)
			update(a[i], d);
	}
	int query(int x) {
		int ans = 0;
		for (int i = x; i >= 1; i -= i & -i)
			update(ans, a[i]);
		return ans;
	}
} BIT;
int n, m, t, x[MAXN], y[MAXN];
pair <int, int> a[MAXN];
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++)
		read(x[i]);
	for (int i = 1; i <= m; i++)
		read(y[i]);
	for (int i = 1; i <= n; i++) {
		int pos = upper_bound(y + 1, y + m + 1, x[i]) - y;
		if (pos == m + 1 || pos == 1) continue;
		a[++t] = make_pair(x[i] - y[pos - 1], y[pos] - x[i]);
	}
	if (t == 0) {
		cout << 1 << endl;
		return 0;
	}
	n = t; int cnt = 0;
	set <int> st; map <int, int> home;
	for (int i = 1; i <= n; i++)
		st.insert(a[i].second);
	for (auto x : st) home[x] = ++cnt;
	for (int i = 1; i <= n; i++)
		a[i].second = home[a[i].second];
	BIT.init(cnt), BIT.modify(1, 1);
	sort(a + 1, a + n + 1);
	n = unique(a + 1, a + n + 1) - a - 1;
	int ans = 1; static int dp[MAXN];
	for (int i = 1, nxt; i <= n; i = nxt + 1) {
		nxt = i; while (nxt + 1 <= n && a[nxt + 1].first == a[i].first) nxt++;
		for (int j = i; j <= nxt; j++) {
			dp[j] = BIT.query(a[j].second);
			update(ans, dp[j]);
		}
		for (int j = i; j <= nxt; j++)
			BIT.modify(a[j].second + 1, dp[j]);
	}
	cout << ans << endl;
	return 0;
}

Atcoder Regular Contest 102

Problem F. Revenge of BBuBBBlesort!

定义一次交换 a i 1 , a i + 1 a_{i-1},a_{i+1} 的操作为操作 i i

可以发现,进行操作 i i 后, a i 1 < a i < a i + 1 a_{i-1}<a_i<a_{i+1} 始终成立,从而操作 i 1 , i + 1 i-1,i+1 均不会出现,因此,位置 i i 上的元素保持不变。

那么,我们可以将数组分为若干个极小的值域与定义域相同的区间,分别判断。

在每一段区间内,条件 “奇数位的值为下标”、“偶数位的值为下标” ,至少要有一者满足。

以 “偶数位的值为下标” 为例,则奇数位不能存在值为下标的位置,否则该位置将不能移动,又因为该区间是极小的值域与定义域相同的区间,从而其一定会阻挡两侧元素的移动。

可以发现,元素一旦经过交换,不能回到原来的位置,因此元素只能向一个方向移动。

因此,答案为 Yes 的必要条件为奇数位要向右的元素和要向左的元素分别单调。

同时,该条件也是充分的,我们可以每次交换一对分别要向右、要向左的元素进行排序。

时间复杂度 O ( N ) O(N)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int a[MAXN];
int main() {
	int n; read(n);
	for (int i = 1; i <= n; i++) {
		read(a[i]);
		if (a[i] % 2 != i % 2) {
			puts("No");
			return 0;
		}
	}
	int last = 0, Max = 0;
	for (int i = 1; i <= n; i++) {
		chkmax(Max, a[i]);
		if (Max == i) {
			bool flg = true, glg = true;
			for (int j = last + 1; j <= i; j++)
				if (a[j] != j) {
					if (j & 1) flg = false;
					else glg = false;
				}
			if (!flg && !glg) {
				puts("No");
				return 0;
			}
			if (flg && !glg) {
				int Max = 0, Nax = 0;
				for (int j = last + 1; j <= i; j++)
					if (j % 2 == 0) {
						if (a[j] == j) {
							puts("No");
							return 0;
						}
						if (a[j] > j) {
							if (a[j] < Max) {
								puts("No");
								return 0;
							}
							Max = a[j];
						} else {
							if (a[j] < Nax) {
								puts("No");
								return 0;
							}
							Nax = a[j];
						}
					}
			}
			if (glg && !flg) {
				int Max = 0, Nax = 0;
				for (int j = last + 1; j <= i; j++)
					if (j % 2 == 1) {
						if (a[j] == j) {
							puts("No");
							return 0;
						}
						if (a[j] > j) {
							if (a[j] < Max) {
								puts("No");
								return 0;
							}
							Max = a[j];
						} else {
							if (a[j] < Nax) {
								puts("No");
								return 0;
							}
							Nax = a[j];
						}
					}
			}
			last = i;
		}
	}
	puts("Yes");
	return 0;
}

Atcoder Regular Contest 103

Problem D. Robot Arms

首先,我们可以初步考虑无解的情况。

引理 1: 若将各个点 ( x , y ) (x,y) 按照 x + y x+y 的奇偶性黑白染色,则对于确定的 d i d_i 数组,无论选择何种 w i w_i ,至多只能到达一种颜色的点。

证明: 由染色的方式,若 d i d_i 为偶数,则无论走向 L , R , U , D L,R,U,D 中的哪一个方向,到达的点均与起始点同色;而若 d i d_i 为奇数,则无论走向 L , R , U , D L,R,U,D 中的哪一个方向,到达的点均与起始点异色。

引理 1 给出了一个无解的充分条件,即存在颜色不同的点。

通过下一节的构造算法,我们可以看到,若引理 1 不能判断输入无解,我们都可以通过该算法构造出一组解,因此这个条件同样是必要的。

由于题目对 M M 的限制很紧,大约只有 1 × l o g 2 ( X i + Y i ) 1\times log_2(|X_i|+|Y_i|) ,可能的构造方式实际上不多。为了在限制内完成构造,可以想到采用 d = { 1 , 2 , 4 , 8 , } d=\{1,2,4,8,\dots\} 的构造方式。

D i D_i 表示 { 2 0 , 2 1 , , 2 i } \{2^0,2^1,\dots,2^{i}\} E i E_i 表示在 D i D_i 中多加入一个 2 0 2^0 得到的集合。

S i S_i 表示对于所有的 w w 的设置方案, D i D_i 可以达到的点集。

T i T_i 表示对于所有的 w w 的设置方案, E i E_i 可以达到的点集。

定理 1:
( x , y ) S i (x,y)\in S_i ,当且仅当 x + y 1   ( m o d   2 ) |x|+|y|\equiv 1\ (mod\ 2) x + y 2 i + 1 |x|+|y|\leq 2^{i+1}

( x , y ) T i (x,y)\in T_i ,当且仅当 x + y 0   ( m o d   2 ) |x|+|y|\equiv 0\ (mod\ 2) x + y 2 i + 1 |x|+|y|\leq 2^{i+1}

证明: 由引理 1 的证明,定理 1 的必要性是显然的,考虑证明其充分性。

考虑归纳法,验证得 i 1 i\leq 1 时,定理 1 成立,因此,我们只需要在定理 1 对 i 1 i-1 成立的条件下证明定理 1 对 i   ( i 2 ) i\ (i\geq 2) 成立。

由于问题在四个象限是对称的,不失一般性地,考虑 x , y 0 x,y\geq0 的情况。

因为 x , y 0 x,y\geq0 x + y 1   ( m o d   2 ) x+y\equiv 1\ (mod\ 2) x + y 2 i + 1 x+y\leq 2^{i+1} x 2 i x\geq 2^i y 2 i y\geq 2^i 中至少有一者成立,不失一般性地,令 x 2 i x\geq 2^i

那么, x 2 i 0 x-2^i\geq 0 ,并且 x 2 i + y 1   ( m o d   2 ) , x 2 i + y 2 i x-2^i+y\equiv 1\ (mod\ 2),x-2^i+y\leq 2^i

因此 ( x 2 i , y ) S i 1 (x-2^i,y)\in S_{i-1} ,而 D i = D i 1 { 2 i } D_i=D_{i-1}\cup \{2^i\} ,从而 ( x , y ) S i (x,y)\in S_{i}

同样地,因为 x , y 0 x,y\geq0 x + y 0   ( m o d   2 ) x+y\equiv 0\ (mod\ 2) x + y 2 i + 1 x+y\leq 2^{i+1} x 2 i x\geq 2^i y 2 i y\geq 2^i 中至少有一者成立,不失一般性地,令 x 2 i x\geq 2^i

那么, x 2 i 0 x-2^i\geq 0 ,并且 x 2 i + y 0   ( m o d   2 ) , x 2 i + y 2 i x-2^i+y\equiv 0\ (mod\ 2),x-2^i+y\leq 2^i

因此 ( x 2 i , y ) T i 1 (x-2^i,y)\in T_{i-1} ,而 E i = E i 1 { 2 i } E_i=E_{i-1}\cup \{2^i\} ,从而 ( x , y ) T i (x,y)\in T_{i}

由定理 1 的证明,对于不能判断无解的输入,我们也可以得到一个可行的构造。

时间复杂度 O ( N × M ) O(N\times M) ,其中 M = 40 M=40

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
bool check(ll x, ll y, ll s) {
	if (x < 0) x = -x;
	if (y < 0) y = -y;
	return x + y <= s;
}
ll x[MAXN], y[MAXN];
int main() {
	int n; read(n);
	int cnt[2] = {0, 0};
	for (int i = 1; i <= n; i++) {
		read(x[i]), read(y[i]);
		cnt[(x[i] + y[i]) % 2 == 0]++;
	}
	if (cnt[0] != 0 && cnt[1] != 0) {
		puts("-1");
		return 0;
	}
	vector <ll> ans, sum;
	ans.push_back(1);
	if (cnt[1]) ans.push_back(1);
	for (int i = 2; i <= 38; i++)
		ans.push_back(1ll << (i - 1));
	sum.push_back(0);
	for (unsigned i = 1; i < ans.size(); i++)
		sum.push_back(sum.back() + ans[i - 1]);
	cout << ans.size() << endl;
	for (auto x : ans)
		printf("%lld ", x);
	printf("\n");
	for (int i = 1; i <= n; i++) {
		string res; res.resize(ans.size());
		ll nowx = x[i], nowy = y[i];
		for (int j = ans.size() - 1; j >= 0; j--) {
			if (check(nowx + ans[j], nowy, sum[j])) {
				res[j] = 'L';
				nowx += ans[j];
			} else if (check(nowx - ans[j], nowy, sum[j])) {
				res[j] = 'R';
				nowx -= ans[j];
			} else if (check(nowx, nowy + ans[j], sum[j])) {
				res[j] = 'D';
				nowy += ans[j];
			} else if (check(nowx, nowy - ans[j], sum[j])) {
				res[j] = 'U';
				nowy -= ans[j];
			}
		}
		cout << res << endl;
	}
	return 0;
}

Problem F. Distance Sums

考虑一条连接 x , y x,y 两点的边 ( x , y ) (x,y) ,记 x x 侧的点数为 s x sx y y 侧的点数为 s y   ( s y = N s x ) sy\ (sy=N-sx) ,则有 D x = D y + s y s x D_x=D_y+sy-sx 。因此,可以发现,叶子节点具有最大的 D i D_i

找到最大的 D i D_i 的点 x x ,我们可以计算出与其直接相连的点的 D i D_i ,由于 D i D_i 互不相同,我们可以直接确定这个点,从而删去 x x 。重复这个考虑过程,我们可以将树构造出来。

由于构造的过程是基于 D i D_i 的相对大小关系的,还需要检查一下 D 1 D_1 是否正确。

时间复杂度 O ( N L o g N ) O(NLogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
ll d[MAXN]; bool vis[MAXN]; map <ll, int> home;
vector <int> a[MAXN]; pair <ll, int> b[MAXN];
int f[MAXN], s[MAXN], size[MAXN];
void dfs(int pos, int fa) {
	size[pos] = 1;
	for (auto x : a[pos])
		if (x != fa) {
			dfs(x, pos);
			size[pos] += size[x];
		}
}
int main() {
	int n; read(n);
	for (int i = 1; i <= n; i++) {
		read(d[i]);
		f[i] = i, s[i] = 1;
		b[i] = make_pair(d[i], i);
		home[d[i]] = i;
	}
	sort(b + 1, b + n + 1);
	reverse(b + 1, b + n + 1);
	for (int i = 1; i <= n - 1; i++) {
		int pos = b[i].second; vis[pos] = true;
		ll another = b[i].first + s[pos] - (n - s[pos]);
		if (home.count(another) == 0 || vis[home[another]]) {
			puts("-1");
			return 0;
		}
		int res = home[another];
		f[pos] = f[res], s[res] += s[pos];
	}
	for (int i = 1; i <= n; i++)
		if (vis[i]) {
			a[i].push_back(f[i]);
			a[f[i]].push_back(i);
		}
	dfs(1, 0);
	ll res = 0;
	for (int i = 2; i <= n; i++)
		res += size[i];
	if (res == d[1]) {
		for (int i = 1; i <= n; i++)
			if (vis[i]) printf("%d %d\n", i, f[i]);
	} else puts("-1");
	return 0;
}
发布了813 篇原创文章 · 获赞 93 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_39972971/article/details/103709145