\(\text{T1 码灵鼠}\)

\(\text{Description}\)

对于数列 \(a\)

  • \(a_0 = 1\)

  • \(a_n = a_i + a_j(n\ge1,i,j\) 均在 \([0,n-1]\) 内均匀随机\()\)

请求出对于给定的 \(n\)\(a_n\) 的期望值是多少。

  • \(0\le n\le2147483647\)

\(\text{Solution}\)

你算一下前三项:

\(a_0=1\)

\(a_1=(a_0+a_0)\div1=2\)

\(a_2=[(a_0+a_0)+(a_0+a_1)+(a_1+a_0)+(a_1+a_1)]\div4=3\)

于是你就猜 \(a_n=n+1\)

然后就对了(

现在我们来用数学归纳法证明一下(过程可能有不严谨之处,请见谅):

\(n=0\) 时,\(a_0=1\),成立。

\(\forall i\in[0,n-1],a_{i-1}=i\) 成立时,设 \(E(x)\)\(x\) 的期望值,我们有

\(\begin{aligned}a_n&=E(a_i+a_J)(\forall i,j\in[0,n-1])\\&=E(a_i)+E(a_j)(\forall i,j\in[0,n-1])\\&=2\times E(a_i)(\forall i\in[0,n-1])\\&=2\times\dfrac{\sum_{i=0}^{n-1}a_i}{n}\\&=2\times\dfrac{\dfrac{1+2+\cdots+n}{2}}{n}\\&=n+1\end{aligned}\)

证毕。

记得特判 \(n=2147483647\)\(n+1\)int 了(不然会丢 \(30\) 分)。

时间复杂度 \(\mathcal{O}(T)\)

\(\text{Code}\)

#include <iostream>
#include <cstdio>
using namespace std;

int main()
{
	int t;
	scanf("%d", &t);
	while (t--)
	{
		int n;
		scanf("%d", &n);
		if (n == 2147483647)
		{
			puts("2147483648");
		}
		else
		{
			printf("%d\n", n + 1);
		}
	}
	return 0;
}

\(\text{T2 So many prefix?}\)

\(\text{Description}\)

给定一个字符串 \(S\),请求出所有长度为偶数的 \(S\) 的前缀在 \(S\) 中出现的次数和。

  • 对于 \(30\%\) 的数据,\(|S|\le100\),保证数据随机;
  • 对于 \(100\%\) 的数据,\(|S|\le200000\)

\(\text{Solution}\)

提供 \(2\) 种思路,剩下一种请咨询 xhj

\(1. \rm{AC}\) 自动机 \(+\) 拓扑建图优化

只考虑偶数的话就和 LUOGU5357 【模板】AC自动机(二次加强版) 差不多了。

对于偶数位上的都给 \(pos\) 打上标记,然后 \(\operatorname{search}\) 时遇上有标记的就不停地跳 \(fail\) 同时将 ans++

这个的时间复杂度是 \(\mathcal{O}(|S|^2)\) 的。

那么加一个拓扑建图优化即可。

时间复杂度 \(\mathcal{O}(|S|)\)

\(2. \rm{KMP+\rm{DP}}\)

\(dp_i\) 为长度为 \(i\) 的前缀的答案。

那么显然有 \(dp_i=\begin{cases}dp_{nxt_i}+1&i\equiv0\pmod{2}\\dp_{nxt_i}&i\equiv1\pmod{2}\end{cases}\)

所以 \(\operatorname{for}\) 一遍边推边求和即可。

时间复杂度 \(\mathcal{O}(|S|)\)

\(\text{Code}\)

\(1.\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;

const int MAXN = 2e5 + 5;

int len, tot, cnt;
int trie[MAXN][26], id[MAXN], fail[MAXN], head[MAXN], siz[MAXN];

struct edge
{
	int to, nxt;
}e[MAXN];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

void insert(char *s)
{
	int pos = 0;
	for (int i = 0; i < len; i++)
	{
		int c = s[i] - 'a';
		if (!trie[pos][c])
		{
			trie[pos][c] = ++tot;
		}
		pos = trie[pos][c];
		if (i & 1)
		{
			id[(i + 1) >> 1] = pos;
		}
	}
}

void build()
{
	queue<int> q;
	for (int c = 0; c < 26; c++)
	{
		if (trie[0][c])
		{
			q.push(trie[0][c]);
		}
	}
	while (!q.empty())
	{
		int u = q.front();
		q.pop();
		for (int c = 0; c < 26; c++)
		{
			int v = trie[u][c];
			if (v)
			{
				fail[v] = trie[fail[u]][c];
				q.push(v);
			}
			else
			{
				trie[u][c] = trie[fail[u]][c];
			}
		}
	}
}

void search(char *s)
{
	int pos = 0, res = 0;
	for (int i = 0; i < len; i++)
	{
		int c = s[i] - 'a';
		pos = trie[pos][c];
		siz[pos]++;
	}
	for (int i = 1; i <= tot; i++)
	{
		add(fail[i], i);
	}
}

void dfs(int u)
{
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		dfs(v);
		siz[u] += siz[v];
	}
}

char s[MAXN];

int main()
{
	scanf("%s", s);
	len = strlen(s);
	insert(s);
	build();
	search(s);
	dfs(0);
	int ans = 0;
	for (int i = 1; i <= (len >> 1); i++)
	{
		ans += siz[id[i]]; //只对偶数求和
	}
	printf("%d\n", ans);
	return 0;
}

\(2.\)

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int MAXN = 2e5 + 5;

char s[MAXN];
int nxt[MAXN], dp[MAXN];

int main()
{
	scanf("%s", s + 1);
	int len = strlen(s + 1);
	for (int i = 2, j = 0; i <= len; i++)
	{
		while (j && s[i] != s[j + 1])
		{
			j = nxt[j];
		}
		if (s[i] == s[j + 1])
		{
			j++;
		}
		nxt[i] = j;
	}
	int ans = 0;
	for (int i = 1; i <= len; i++)
	{
		dp[i] = dp[nxt[i]] + !(i & 1);
		ans += dp[i];
	}
	printf("%d\n", ans);
	return 0;
}

\(\text{T3 TRAVEL}\)

\(\text{Description}\)

CF366D Dima and Trap Graph

在一张无向图上有 \(N\) 个点和 \(M\) 条边,每条边连接着点 \(a\) 和点 \(b\),有无数个人编号为 \(1,2,\dots,+\infty\)。通过某条边是要一定的条件的,对于每条边,有两个值 \(l\)\(r\),表示该条边只允许编号在 \([l,r]\) 区间内的人通过,从 \(1\) 号点到达 \(N\) 号点,请求出最多有多少人可以通过,并求出这些人的编号(若有多组解取字典序最小的一组 )

  • 可能有重边和自环。
  • 如果没有人能通过只要输出 0 就可以了。
  • 对于 \(30\%\) 的数据,\(1\le N,M\le10\)
  • 对于 \(100\%\) 的数据,\(2\le N\le1000,0\le M\le3000,1\le a,b\le N,1\le l\le r\le10^6\)

\(\text{Solution}\)

依然有两种思路(注意字典序最小的判断):

\(1.\) 最长路

遍历每一条边,对于第 \(i\) 条边,选取边权 \(\ge l_i\) 的边,从 \(1\)\(N\) 跑最长路,让 \(r\) 最大,在 \(l\) 固定的情况下,肯定是 \(r\) 越大越好。

时间复杂度 \(\mathcal{O}(M\cdot M\log N)=\mathcal{O}(M^2\log N)\)

\(2.\) 并查集

同样是遍历每一条边 \(i\),将所有满足 \(l_j\le l_i\)\(j\),都把 \(a_j\)\(b_j\) 并入一个集合。

那么最终的 \(l\) 一定是 \(l_i\),所以把所有的 \(r_i\) 取个 \(\min\) 作为最终的 \(r\),当点 \(1\) 和点 \(N\) 在同一个集合内说明可以尝试去更新答案。

因为是并查集实现,结合路径压缩和按秩合并可以做到 \(\mathcal{O}(M^2\alpha(N))\),不过因为 \(N\le1000\) 所以优化不大,主要优化在于常数(

时间复杂度 \(\mathcal{O}(M^2\alpha(N))\)

\(\text{Code}\)

\(1.\)

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;

const int MAXN = 1005;
const int MAXM = 3005;
const int INF = 0x3f3f3f3f;

int cnt;
int head[MAXN];

struct edge
{
	int to, l, r, nxt;
}e[MAXM << 1];

void add(int u, int v, int l, int r)
{
	e[++cnt] = edge{v, l, r, head[u]};
	head[u] = cnt;
}

int dis[MAXN];
bool vis[MAXN];

struct node
{
	int u, r;
	bool operator <(const node &x)const
	{
		if (x.r == r)
		{
			return x.u < u;
		}
		return x.r > r;
	}
};

void init()
{
	memset(dis, -INF, sizeof(dis));
	memset(vis, false, sizeof(vis));
}

void dijkstra(int l)
{
	init();
	dis[1] = INF;
	priority_queue<node> pq;
	pq.push(node{1, dis[1]});
	while (!pq.empty())
	{
		int u = pq.top().u;
		pq.pop();
		if (vis[u])
		{
			continue;
		}
		vis[u] = true;
		for (int i = head[u]; i; i = e[i].nxt)
		{
			if (e[i].l > l) //取在区间内的
			{
				continue;
			}
			int v = e[i].to;
			if (dis[v] < min(dis[u], e[i].r)) //r要取min
			{
				dis[v] = min(dis[u], e[i].r);
				pq.push(node{v, dis[v]});
			}
		}
	}
}

int l[MAXM], r[MAXM];

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int u, v;
		scanf("%d%d%d%d", &u, &v, l + i, r + i);
		add(u, v, l[i], r[i]);
		add(v, u, l[i], r[i]);
	}
	int ansk = 0, ansl = 0, ansr = 0;
	for (int i = 1; i <= m; i++)
	{
		dijkstra(l[i]);
		int res = dis[n] - l[i] + 1;
		if (ansk < res || (ansk == res && ansl > l[i]))
		{
			ansk = res;
			ansl = l[i];
			ansr = dis[n];
		}
	}
	if (ansk)
	{
		printf("%d\n", ansk);
		for (int i = ansl; i <= ansr; i++)
		{
			printf("%d ", i);
		}
	}
	else
	{
		puts("0");
	}
	return 0;
}

\(2.\)

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN = 1005;
const int MAXM = 3005;
const int INF = 0x3f3f3f3f;

struct edge
{
	int u, v, l, r;
}e[MAXM];

bool cmp(edge x, edge y)
{
	if (x.r == y.r)
	{
		return x.l < y.l;
	}
	return x.r > y.r;
}

int n, m, r, ansk, ansl, ansr;
int fa[MAXN], siz[MAXN];

void init()
{
	for (int i = 1; i <= n; i++)
	{
		fa[i] = i;
		siz[i] = 1;
	}
}

int find(int x)
{
	if (x == fa[x])
	{
		return x;
	}
	return fa[x] = find(fa[x]); //路径压缩
}

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d%d%d", &e[i].u, &e[i].v, &e[i].l, &e[i].r);
	}
	sort(e + 1, e + m + 1, cmp);
	for (int i = 1; i <= m; i++)
	{
		init();
		r = INF;
		for (int j = 1; j <= m; j++)
		{
			if (e[j].l <= e[i].l)
			{
				int x = find(e[j].u), y = find(e[j].v);
				if (x != y)
				{
					if (siz[x] > siz[y]) //按秩合并
					{
						fa[y] = x;
						siz[x] += siz[y];
					}
					else
					{
						fa[x] = y;
						siz[y] += siz[x];
					}
					r = min(r, e[j].r);
					if (find(1) == find(n))
					{
						break;
					}
				}
			}
		}
		if (find(1) == find(n) && (ansk < r - e[i].l + 1 || (ansk == r - e[i].l + 1 && ansl > e[i].l)))
		{
			ansk = r - e[i].l + 1;
			ansl = e[i].l;
			ansr = r;
		}
	}
	if (ansk)
	{
		printf("%d\n", ansk);
		for (int i = ansl; i <= ansr; i++)
		{
			printf("%d ", i);
		}
	}
	else
	{
		puts("0");
	}
	return 0;
}

\(\text{T4 潜入行动}\)

\(\text{Description}\)

LUOGU4516 [JSOI2018]潜入行动

有一棵 \(n\) 个节点、\(n−1\) 条边的树,如果在节点 \(u\) 上安装监听设备,则J能够监听与 \(u\) 直接相邻所有的节点。特别注意放置在节点 \(u\) 的监听设备并不监听 \(u\) 本身。一共有了 \(k\) 个监听设备,请求出有多少种不同的放置监听设备的方法,能够使得树上所有节点都被监听。

  • 每个节点至多只能安装一个监听设备,且监听设备必须被用完。

  • 输出答案 \(\mod1000000007\) 的余数。

  • 存在 \(10\%\) 的数据,\(1\le n\le20\)

  • 存在另外 \(10\%\) 的数据,\(1\le n\le100\)

  • 存在另外 \(10\%\) 的数据,\(1\le k\le10\)

  • 存在另外 \(10\%\) 的数据,输入的树保证是一条链;

  • 对于所有数据,\(1\le n\le105\)\(1\le k\le \min\{n,100\}\)

\(\text{Solution}\)

这种题一看就是 \(\rm DP\)

\(dp_{u,x,0/1,0/1}\) 表示节点 \(u\) 的子树内有 \(x\) 个监听设备,且节点 \(u\) 是否安了监听设备,节点 \(u\) 是否被监听。

然后就是推柿子了(以下省略过程)设 \(v\)\(u\) 的儿子。

\(dp_{u,j+l,0,0}=dp_{u,j,0,0}\times dp_{v,l,0,1}\)

\(dp_{u,j+l,0,1}=dp_{u,j,0,1}\times(dp_{v,l,0,1}+dp_{v,l,1,1})+dp_{u,j,0,0}\times dp_{v,l,1,1}\)

\(dp_{u,j+l,1,0}=dp_{u,j,1,0}\times(dp_{v,l,0,0}+dp_{v,l,0,1})\)

\(dp_{u,j+l,1,1}=dp_{u,j,1,0}\times(dp_{v,l,1,0}+dp_{v,l,1,1})+dp_{u,j,1,1}\times(dp_{v,l,0,0}+dp_{v,l,0,1}+dp_{v,l,1,0}+dp_{v,l,1,1})\)

注意事项:

  1. \(dp_{u,j,0/1,0/1}\) 这个要预先存起来,不然用来更新 \(dp_{u,j+l,0/1,0/1}\) 的不是上一轮的了。;
  2. 直接开 long long\(\rm MLE\)。需要在计算时乘上 1LL

\(\text{Code}\)

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 1e5 + 5;
const int MOD = 1e9 + 7;

int n, k, cnt;
int head[MAXN], siz[MAXN], dp[MAXN][105][2][2], tmp[105][2][2];

struct edge
{
	int to, nxt;
}e[MAXN << 1];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

void dfs(int u, int fa)
{
	siz[u] = dp[u][0][0][0] = dp[u][1][1][0] = 1; //初始化
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (v == fa)
		{
			continue;
		}
		dfs(v, u);
		for (int j = 0; j <= min(siz[u], k); j++)
		{
			tmp[j][0][0] = dp[u][j][0][0], dp[u][j][0][0] = 0;
			tmp[j][0][1] = dp[u][j][0][1], dp[u][j][0][1] = 0;
			tmp[j][1][0] = dp[u][j][1][0], dp[u][j][1][0] = 0;
			tmp[j][1][1] = dp[u][j][1][1], dp[u][j][1][1] = 0;
		}
		for (int j = 0; j <= min(siz[u], k); j++)
		{
			for (int l = 0; l <= min(siz[v], k - j); l++)
			{
				dp[u][j + l][0][0] = (dp[u][j + l][0][0] + 1LL * tmp[j][0][0] * dp[v][l][0][1] % MOD) % MOD;
				dp[u][j + l][0][1] = (dp[u][j + l][0][1] + 1LL * tmp[j][0][1] * (1LL * dp[v][l][0][1] + 1LL * dp[v][l][1][1]) % MOD + 1LL * tmp[j][0][0] * dp[v][l][1][1] % MOD) % MOD;
				dp[u][j + l][1][0] = (dp[u][j + l][1][0] + 1LL * tmp[j][1][0] * (1LL * dp[v][l][0][0] + 1LL * dp[v][l][0][1]) % MOD) % MOD;
				dp[u][j + l][1][1] = (dp[u][j + l][1][1] + 1LL * tmp[j][1][0] * (1LL * dp[v][l][1][0] + 1LL * dp[v][l][1][1]) % MOD + 1LL * tmp[j][1][1] * (1LL * dp[v][l][0][0] + 1LL * dp[v][l][0][1] + 1LL * dp[v][l][1][0] + 1LL * dp[v][l][1][1]) % MOD) % MOD;
			}
		}
		siz[u] += siz[v];
	}
}

int main()
{
	scanf("%d%d", &n, &k);
	for (int i = 1; i < n; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v);
		add(v, u);
	}
	dfs(1, 0);
	printf("%d\n", (dp[1][k][0][1] + dp[1][k][1][1]) % MOD); //最终1号点必须被监视
	return 0;
}