\(Day2\)

\(A\)

概率期望经典入门题,根据期望的线性性,答案就是每个点被选到的期望之和,每个点的贡献为一,所以只需要求每个点被选到的概率即可,每个点必须在它的子树中的点被选之前选,所以概率是\(\frac{1}{size_i}\)

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int MOD = 998244353;
const int N = 10000000;

int n, head[N + 50], num, inv[N + 50], siz[N + 50], f[N + 50];

void Read(int &x)
{
	x = 0; int p = 0; char st = getchar();
	while (st < '0' || st > '9') p = (st == '-'), st = getchar();
	while (st >= '0' && st <= '9') x = (x << 1) + (x << 3) + st - '0', st = getchar();
	x = p ? -x : x;
	return; 
}  

struct Node
{
	int next, to;
} edge[N * 2 + 50];

void Addedge(int u, int v)
{
	edge[++num] = (Node){head[u], v};
	head[u] = num;
	return;
}

int main()
{
	Read(n);
	inv[1] = 1;
	for (int i = 2; i <= n; i++) Read(f[i]), inv[i] = 1LL * (MOD - MOD / i) * inv[MOD % i] % MOD;
	for (int i = n; i >= 1; i--) siz[i]++, siz[f[i]] += siz[i];
	int ans = 0; 
	for (int i = 1; i <= n; i++) (ans += inv[siz[i]]) %= MOD;
	printf("%d", ans); 
	return 0;
}

\(B\)

难得一见的构造题,对于偶数,随便取出来一个使得剩下的变成奇数个。

对于奇数,按\(a\)排序,先取\(a\)最大的一个,然后剩下的从前往后两两分为一组,每组选\(b\)较大的一个。

这样一定是正确的因为每组\(b\)都选了较大的,最劣情况下选的和没选的相同,但是另外还选了一个元素,所以\(b\)的总和一定大于所有的\(b\)的一半;因为是按照\(a\)排序两两一组,最劣情况下每组都选了较小的\(a\),但是因为我们已经选了最大的\(a\),就相当于可以给元素重新分组,从后往前两两一组,这样就可以和\(b\)一样证明。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100000;

int n;

struct Node
{
	int x, y, id;
}  a[N + 50];

int Cmp(Node a, Node b)
{
	return a.x < b.x;
}

void Read(int &x)
{
	x = 0; int p = 0; char st = getchar();
	while (st < '0' || st > '9') p = (st == '-'), st = getchar();
	while (st >= '0' && st <= '9') x = (x << 1) + (x << 3) + st - '0', st = getchar();
	x = p ? -x : x;
	return; 
} 

int main()
{
	Read(n);
	for (int i = 1; i <= n; i++) Read(a[i].x), a[i].id = i;
	for (int i = 1; i <= n; i++) Read(a[i].y);
	printf("%d\n", n / 2 + 1);
	sort(a + 1, a + n + 1, Cmp);
	if (n % 2 == 0) printf("%d ", a[n].id), n--;
	printf("%d ", a[n].id); n--;
	for (int i = 1; i <= n - 1; i += 2) printf("%d ", a[i].y > a[i + 1].y ? a[i].id : a[i + 1].id); 
	return 0;
}

\(C\)

第一种限制可以求出每个点子树内染色的下限,第二种限制通过二分可以求出每个点子树内染色的上限,二分判定即可。

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int N = 100000;
const int INF = 0x7fffffff;

int n, head[N + 50], num, siz[N + 50], minn[N + 50], maxx[N + 50], mid, flag, xx[N + 50][2], mm[N + 50];

struct Node
{
	int next, to;
} edge[N * 2 + 50];

void Addedge(int u, int v)
{
	edge[++num] = (Node){head[u], v};
	head[u] = num;
	return;
}

void Read(int &x)
{
	x = 0; int p = 0; char st = getchar();
	while (st < '0' || st > '9') p = (st == '-'), st = getchar();
	while (st >= '0' && st <= '9') x = (x << 1) + (x << 3) + st - '0', st = getchar();
	x = p ? -x : x;
	return;
}  

void Dfs(int x, int fa)
{
	siz[x] = 1;
	for (int i = head[x]; i; i = edge[i].next)
	{
		int v = edge[i].to;
		if (v == fa) continue;
		Dfs(v, x);
		siz[x] += siz[v];
	}
	return;
}

void Dfspd(int x, int fa)
{
	mm[x] = mid - xx[x][1];
	int tmp = 1;
	for (int i = head[x]; i; i = edge[i].next)
	{
		int v = edge[i].to;
		if (v == fa) continue;
		Dfspd(v, x); tmp += mm[v];
	}
	mm[x] = min(mm[x], tmp);
	if (mm[x] < xx[x][0]) flag = 0;
	return;
}

void Dfs2(int x, int fa)
{
	int tmp = 0;
	for (int i = head[x]; i; i = edge[i].next)
	{
		int v = edge[i].to;
		if (v == fa) continue;
		Dfs2(v, x);
		tmp += xx[v][0];
	}
	xx[x][0] = max(tmp, xx[x][0]);
	return;
}

int main()
{
	Read(n);
	for (int i = 1, u, v; i <= n - 1; i++)
	{
		Read(u); Read(v);
		Addedge(u, v); Addedge(v, u);
	}
	int a, b;
	Dfs(1, 0);
	Read(a);
	for (int i = 1; i <= a; i++)
	{
		int x, y;
		Read(x); Read(y);
		xx[x][0] = max(xx[x][0], y);
		if (y > siz[x]) { puts("-1"); return 0; }
	}
	Read(b);
	for (int i = 1; i <= b; i++)
	{
		int x, y;
		Read(x); Read(y);
		xx[x][1] = max(xx[x][1], y);
		if (y > n - siz[x]) { puts("-1"); return 0; }
	}
	Dfs2(1, 0);
	int l = 0, r = n + 50;
	while (l < r)
	{
		mid = (l + r) >> 1;
		flag = 1;
		Dfspd(1, 0);
		if (flag) r = mid;
		else l = mid + 1;
	}
	printf("%d\n", l);
	return 0;
}

\(D\)

先转移出来可以随便修改决策时,两个人分别拿到最多\(i\)分的方案数。

然后注意到如果钦定一条重链,两个人的决策其实是独立的,所以再根据上面的东西转移出来走到\(x\)点时,每个人最后得分为i的决策数,在叶子节点相乘即可。

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int N = 5000;
const int MOD = 998244353;

int n, k, g1[N + 50][N + 50], g2[N + 50][N + 50], bin[N + 50], ans, f1[N + 50][N + 50], f2[N + 50][N + 50];

struct Node
{
	int son[2];
} tr[N + 50];

void Addedge(int u, int v)
{
	if (!tr[u].son[0]) tr[u].son[0]	= v;
	else tr[u].son[1] = v;
	return;
} 

void Read(int &x)
{
	x = 0; int p = 0; char st = getchar();
	while (st < '0' || st > '9') p = (st == '-'), st = getchar();
	while (st >= '0' && st <= '9') x = (x << 1) + (x << 3) + st - '0', st = getchar();
	x = p ? -x : x;
	return; 
} 

void Dfs1(int x, int y)
{
	if (!tr[x].son[0]) 
	{
		for (int i = 1; i <= k; i++) g1[x][i] = g2[x][i] = i * k;//显然小于等于i的得分下该部分可以有i种选择,另外一部分随便选择。 
		bin[x] = k * k; 
		return;
	}
	Dfs1(tr[x].son[0], y ^ 1); Dfs1(tr[x].son[1], y ^ 1);//记录当前节点由谁控制。 
	int s0 = tr[x].son[0], s1 = tr[x].son[1];
	bin[x] = 1LL * bin[s0] * bin[s1] % MOD * 2 % MOD;
	if (y)
	{
		for (int i = 1; i <= k; i++)
			g2[x][i] = 1LL * g2[s0][i] * g2[s1][i] % MOD * 2 % MOD, //乘2是因为要从两个儿子中选择一个当作重儿子,两个都得小于等于i是因为决策点可以任意改变。 
			g1[x][i] = (0LL + 1LL * g1[s0][i] * bin[s1] % MOD + 1LL * g1[s1][i] * bin[s0] % MOD) % MOD; //另外一个只能被钦定是走右边还是走左边,除了钦定的以外的另外一边因为觉决策点不会改变所以可以随便走。 
	}
	else
	{
		for (int i = 1; i <= k; i++)
			g1[x][i] = 1LL * g1[s0][i] * g1[s1][i] % MOD * 2 % MOD,
			g2[x][i] = (0LL + 1LL * g2[s0][i] * bin[s1] % MOD + 1LL * g2[s1][i] * bin[s0] % MOD) % MOD;
	}
	return;
}

void Dfs2(int x, int y)
{
	if (!tr[x].son[0])
	{
		int tmp1 = 0, tmp2 = 0;
		for (int i = 1; i <= k; i++) tmp1 = (0LL + tmp1 + f1[x][i]) % MOD, tmp2 = (0LL + tmp2 + f2[x][i]) % MOD;
		ans = (0LL + ans + 1LL * tmp1 * tmp2 % MOD) % MOD;
		return;
	}
	int s0 = tr[x].son[0], s1 = tr[x].son[1];
	if (y)
	{
		for (int i = 1; i <= k; i++)
			f2[s0][i] = 1LL * f2[x][i] * g2[s1][i] % MOD, f2[s1][i] = 1LL * f2[x][i] * g2[s0][i] % MOD,//可以执行决策的另外一边随便选。 
			f1[s0][i] = f1[x][i], f1[s1][i] = f1[x][i];//因为这里是独立的,所以不能执行决策就直接赋值。 
	}
	else
	{
		for (int i = 1; i <= k; i++)
			f1[s0][i] = 1LL * f1[x][i] * g1[s1][i] % MOD, f1[s1][i] = 1LL * f1[x][i] * g1[s0][i] % MOD, 
			f2[s0][i] = f2[x][i], f2[s1][i] = f2[x][i];
	}
	Dfs2(tr[x].son[0], y ^ 1); Dfs2(tr[x].son[1], y ^ 1);
	return; 
}

int main()
{
	Read(n); Read(k);
	for (int i = 2, fa; i <= n; i++) Read(fa), Addedge(fa, i);
	Dfs1(1, 0);
	for (int i = 1; i <= k; i++) f1[1][i] = f2[1][i] = 1;//这里两个人的决策是独立的。 
	Dfs2(1, 0);
	printf("%d", ans);
	return 0;
}
*/ 

\(Day3\)

吐槽:这场要是打了就能阿珂……

可惜打不得。

\(A\)

\(k <= n / 2\)的情况只需要枚举统计,\(k > n / 2\)的时候发现形成了循环节,大小为\(n - k\),于是统计下每个位置每个字母的出现次数把字母都修改成出现次数最多的那个即可。

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

int n, k, tong[50];

char st[100050];

int main()
{
	scanf("%d%d", &n, &k);
	scanf("%s", st + 1);
	if (k <= n / 2)
	{
		int ans = 0;
		for (int i = 1; i <= k; i++)
			ans += (st[i] != st[n - k + i]);
		printf("%d", ans);
		return 0;
	}
	int ans = 0;
	for (int qh = 1; qh <= n - k; qh++) 
	{
		memset(tong, 0, sizeof(tong));
		for (int i = qh; i <= n; i += n - k)
			tong[st[i] - 'a']++;
		int maxx = -1, sum = 0;
		for (int i = 0; i < 26; i++) maxx = max(maxx, tong[i]), sum += tong[i];
		ans += sum - maxx;
	}
	printf("%d", ans);
	return 0;
}

\(B\)

首先打个表格看看能得到什么。

\[1 \oplus 1 = 0, 1 \& 1 = 1, 1 | 1 = 1 \]
\[1 \oplus 0 = 1, 1 \& 0 = 0, 1 | 0 = 1 \]
\[0 \oplus 0 = 0, 0 \& 0 = 0, 0 | 0 = 0 \]

容易发现如果某一位上是\(\&\)运算,那么需要一次\(1\)\(0\)做运算得到;如果某一位是\(|\)运算,那么需要一次\(1\)\(0\)做运算和一次\(1\)\(1\)做运算得到;\(\oplus\)运算和\(|\)运算同理。

那么每一位都需要两个\(1\)和一个\(0\),然后就随便做了。

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

int n, m, sum[550][2];

char st[550];

int main()
{
	int t;
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; i++)
		{
			scanf("%s", st + 1);
			for (int j = 1; j <= m; j++)
				sum[j][st[j] - '0']++;
		}
		int ans = 0;
		for (int i = 1; i <= m; i++)
		{
			int tmp = 0;
			if (sum[i][0] < 1) tmp++;
			if (sum[i][1] < 2) tmp += 2 - sum[i][1];
			ans = max(ans, tmp);
		}
		printf("%d\n", ans);
		for (int i = 1; i <= m; i++)
			for (int j = 0; j <= 1; j++)
				sum[i][j] = 0;
	}
	return 0;
}

\(C\)

注意到值域奇奇怪怪的,那么转变后的数列的值域肯定不会超过\(60\),因为如果变成\(> 60\)的数肯定没有变成全为\(1\)的数列优秀。

我们只关心当前变成的数列有多少质因子被用过,\(60\)以内的质因子只有\(17\)个,状压就好了,然后可以预处理下每个数包含的质因子有哪些,以及哪些状态可以转移到它,这题还是很\(naive\)的。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <cmath>

using namespace std;

const int INF = 0x7f7f7f7f;

int n, a[105], dp[105][132071], nxt[105][132071], zt[70], zs[18] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59}, all, sj[105][132071];

void Read(int &x)
{
	x = 0; int p = 0; char st = getchar();
	while (st < '0' || st > '9') p = (st == '-'), st = getchar();
	while (st >= '0' && st <= '9') x = (x << 1) + (x << 3) + st - '0', st = getchar();
	x = p ? -x : x;
	return;
}

int Gcd(int a, int b)
{
	return a % b == 0 ? b : Gcd(b, a % b);
}

int Abs(int x)
{
	return x < 0 ? -x : x;
}

void Print(int n, int pos)
{
	if (n > 1) Print(n - 1, nxt[n][pos]);
	printf("%d ", sj[n][pos]);
	return;
}

void Prework()
{
	all = (1 << 17) - 1;
	for (int i = 1; i <= 60; i++)
		for (int j = 0; j <= 15; j++)
			if (i % zs[j] == 0) zt[i] += (1 << j);
	return;
}

int main()
{
	Prework();
	Read(n);
	for (int i = 1; i <= n; i++) Read(a[i]);
	memset(dp, 0x7f, sizeof(dp));
	for (int i = 1; i <= 60; i++) 
		if (Abs(a[1] - i) < dp[1][zt[i]])
			dp[1][zt[i]] = Abs(a[1] - i), sj[1][zt[i]] = i;
	for (int i = 2; i <= n; i++)
		for (int lst = 0; lst <= all; lst++)
			if (dp[i - 1][lst] != INF)
				for (int now = 1; now <= 60; now++)
					if ((zt[now] & lst) == 0)
						if (dp[i - 1][lst] + Abs(now - a[i]) < dp[i][zt[now] | lst])
						{
							dp[i][zt[now] | lst] = dp[i - 1][lst] + Abs(now - a[i]);
							nxt[i][zt[now] | lst] = lst; sj[i][zt[now] | lst] = now;
							if (now == 8 && n == 8) cout << dp[n][zt[8] | lst] << endl;
						}
	int ans = INF, pos = 0;
	for (int i = 0; i <= all; i++) 
		if (dp[n][i] < ans)
		{
			ans = dp[n][i];
			pos = i;
		}
	Print(n, pos);
	return 0;
}

\(D\)

转化一下问题,如果从左往右枚举\(x\),将出现过的数加进一个集合,那么如果是“终焉”,就必定存在在集合里的\(a\)\(x * 2 - a\)未曾出现过。

注意到\(a\)\(x * 2 - a\)在值域上构成了回文,出现过为\(1\),未出现为\(0\),那么只需要快速检查两个字符串是否相等,如果不相等则是“终焉”。

判断相等可以使用线段树维护哈希值。

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int N = 300000;
const int HashMOD = 1000000007;
const int HashMiu = 3;

int n, has[N + 50], a[N + 50];

void Read(int &x)
{
	x = 0; int p = 0; char st = getchar();
	while (st < '0' || st > '9') p = (st == '-'), st = getchar();
	while (st >= '0' && st <= '9') x = (x << 1) + (x << 3) + st - '0', st = getchar();
	x = p ? -x : x;
	return; 
}

struct SegmentTree
{
	int suml[(N << 2) + 50], sumr[(N << 2) + 50];
	
	void Update(int k, int l, int r, int pos, int v)
	{
		if (l == r)
		{
			suml[k] = v;
			sumr[k] = v;
			return;
		}
		int mid = (l + r) >> 1;
		if (pos <= mid) Update(k << 1, l, mid, pos, v);
		else Update(k << 1 | 1, mid + 1, r, pos, v);
		suml[k] = (1LL * suml[k << 1] * has[r - mid] % HashMOD + suml[k << 1 | 1]) % HashMOD;
		sumr[k] = (1LL * sumr[k << 1 | 1] * has[mid - l + 1] % HashMOD + sumr[k << 1]) % HashMOD;
		return;
	}
	
	int Queryl(int k, int l, int r, int x, int y)
	{
		if (x > y) return 0;
		if (x <= l && r <= y) return suml[k];
		int mid = (l + r) >> 1;
		if (y <= mid) return Queryl(k << 1, l, mid, x, y);
		else if (x > mid) return Queryl(k << 1 | 1, mid + 1, r, x, y);
		else return (1LL * Queryl(k << 1, l, mid, x, mid) * has[y - mid] % HashMOD + Queryl(k << 1 | 1, mid + 1, r, mid + 1, y)) % HashMOD; 
	}
	
	int Queryr(int k, int l, int r, int x, int y)
	{
		if (x > y) return 0;
		if (x <= l && r <= y) return sumr[k];
		int mid = (l + r) >> 1;
		if (y <= mid) return Queryr(k << 1, l, mid, x, y);
		else if (x > mid) return Queryr(k << 1 | 1, mid + 1, r, x, y);
		else return (1LL * Queryr(k << 1 | 1, mid + 1, r, mid + 1, y) * has[mid - x + 1] % HashMOD + Queryr(k << 1, l, mid, x, mid)) % HashMOD; 
	}
	
	void Clear()
	{
		memset(suml, 0, sizeof(suml));
		memset(sumr, 0, sizeof(sumr));
		return;
	}
} tr;

int main()
{
	has[0] = 1;
	for (int i = 1; i <= N; i++) has[i] = 1LL * has[i - 1] * HashMiu % HashMOD;  
	int t;
	Read(t);
	while (t--)
	{
		Read(n);
		tr.Clear();
		int flag = 0;
		for (int i = 1; i <= n; i++) Read(a[i]);
		for (int i = 1, x; i <= n; i++)
		{
			x = a[i];
			int len = min(x - 1, n - x);	if (tr.Queryl(1, 1, n, x - len, x - 1) != tr.Queryr(1, 1, n, x + 1, x + len)) 
			{	
				flag = 1;
				break;
			}
			tr.Update(1, 1, n, x, 1);
		}
		if (flag) puts("YES"); else puts("NO"); 
	}
	return 0;	
}