ZR19CSP-S赛前冲刺day4

为了保护ZR的版权,这里不提供题目QWQ

http://zhengruioi.com/contest/440/problem/1145 (你进得去吗/xyx)

A 路径

考虑链上如何构造哈密顿回路,很明显就是隔一个跳,然后再跳回来

在这里插入图片描述

大概就是这样

考虑扩展到树上

发现同理
好像就是黑白染色吧

code:


#include<bits/stdc++.h>
#define N 1000005
using namespace std;
struct edge {
	int v, nxt;
}e[N << 1];
int p[N], eid;
void init() {
	memset(p, -1, sizeof p);
	eid = 0;
}
void insert(int u, int v) {
	e[eid].v = v;
	e[eid].nxt = p[u];
	p[u] = eid ++;
}
int n, ANS[N], tot;
void dfs(int u, int dis, int fa) {//dis : 0/1
	if(dis == 0) {
		ANS[++ tot] = u;
		for(int i = p[u]; i + 1; i = e[i].nxt) {
			int v = e[i].v;
			if(v == fa) continue;
			dfs(v, 1, u);
		}
	} else {
		for(int i = p[u]; i + 1; i = e[i].nxt) {
			int v = e[i].v;
			if(v == fa) continue;
			dfs(v, dis - 1, u);
		}
		ANS[++ tot] = u;
	}
}
int main() {
	init();
	scanf("%d", &n);
	for(int i = 1; i < n; i ++) {
		int u, v;
		scanf("%d%d", &u, &v);
		insert(u, v);
		insert(v, u);
	}
	dfs(1, 1, 1);
	if(tot != n) printf("No");
	else {
		printf("Yes\n");
		for(int i = 1; i <= tot; i ++) printf("%d ", ANS[i]);
	}
	return 0;
}

B 魔法

首先发现 m m 很小,可以直接用KMP匹配,求出那些区间至少要挖掉一个(对于相同的右端点只用保存一个最大的左端点),然后问题就转化成了给出若干区间,然后每个区间至少要挖掉一个,求最小要挖掉多少个

然后发现这个东东可以 D P DP
f [ i ] i 设f[i]表示一定要取第i个,并且满足前面的要求的方案数,然后转移的话就是
f [ i ] = min ( f [ j ] ) + a [ i ] , j i 1 f[i] = \min(f[j]) + a[i], 然后j的范围上一个必须取的区间的左端点到i-1这段区间里
然后发现可以用单调队列优化
然后这题就没了

code:

#include<bits/stdc++.h>
#define N 1000005
using namespace std;
int a[N], nxt[N], n, m, L[N], f[N], q[N]; 
char st[N], stt[N];
int main() {
	scanf("%d%d", &n, &m);
	scanf("%s", stt + 1);
	for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
	while(m --) {
		scanf("%s", st + 1);
		int j = 0;
		int len = strlen(st + 1);
		nxt[1] = 0;
		for(int i = 2; i <= len; i ++) {
			while(j && st[j + 1] != st[i]) j = nxt[j];
			if(st[j + 1] == st[i]) j ++;
			nxt[i] = j;
		}
		
		j = 0;
		for(int i = 1; i <= n; i ++) {
			while(j && st[j + 1] != stt[i]) j = nxt[j];
			if(st[j + 1] == stt[i]) j ++;
			if(j == len) {
				L[i] = max(L[i], i - len + 1);//保存最大的左端点
				j = nxt[j];
			}
		}
	}//以上是KMP板子
	
	for(int i = 1; i <= n + 1; i ++) L[i] = max(L[i], L[i - 1]);
	int l = 1, r = 1; q[1] = 0;
	for(int i = 1; i <= n + 1; i ++) {//这是一个比较巧妙的处理,转移到n + 1
		while(l <= r && q[l] < L[i - 1]) l ++;
		f[i] = f[q[l]] + a[i];//转移
		while(l <= r && f[q[r]] >= f[i]) r --;
		q[++ r] = i;
	}
	printf("%d", f[n + 1]);
	return 0;
}

C 交集

神仙题!!!
当时听评价忍不住叫出了 : 女少口阿

首先发现 u , v u,v 是独立的, 对于 u u 来说就是求在 u u 的子树中选 k k 个点使得两两点的 L C A LCA u u , 对于 v v 做一个相同的东西,然后乘起来就行了

如果 u , v u, v 是祖孙关系,那不妨设 u u v v 的祖先,那么 u u 的子树就要改为以 v v
方向作为根方向前提下的子树(即讲 v v 方向的子树砍掉,然后以 u u 做根的子树)

发现为了使两两点之间的 L C A LCA u u , u u 的每个子树中最多取一个点,所以可以做一个01背包,表示如果当前子树选就把方案数乘子树大小。
然后放在 u u 上的情况直接枚举个数然后算一下组合数就好了(其实就一个fac)

这样做的时间复杂度还是会爆炸

注意到度数 < = 50 <= 50

考虑背包的过程,其实就是多项式乘法,对于一个子树 v v 的转移就相当于是原多项式乘上 ( 1 + s i z e [ v ] X ) (1+ size[v] X)
X k X ^ k 的系数表示的就是选 k k 个的方案数

所以对于 u u 的背包可以变成若干个形如 ( 1 + s i z e [ v ] X ) (1 + size[v]X)的一次多项式 相乘的东西,这个是满足交换律的

可以先把 u u 的每个方向的子树做多项式乘法,乘成一个多项式,然后前面的去掉一颗子树就相当于是除一个形如 ( 1 + s i z e [ v ] X ) (1 + size[v]X) 的多项式

乘法和除法都是可以O(L)进行的

最后记得枚举在 u , v u, v 的个数
然后这题就没了

#include<bits/stdc++.h>
#define mod 998244353
#define int long long 
#define N 200005
using namespace std;
int qpow(int x, int y) {
	int ret = 1;
	for(; y; y >>= 1, x = x * x % mod) if(y & 1) ret = ret * x % mod;
	return ret;
}
struct edge {
	int v, nxt;
}e[N << 1];
int p[N], eid;
void init() {
	memset(p, -1, sizeof p);
	eid = 0;
}
void insert(int u, int v) {
	e[eid].v = v;
	e[eid].nxt = p[u];
	p[u] = eid ++;
}
void mul(int *f, int *g, int x, int len) { //乘一个一次多项式
	for(int i = len + 1; i >= 1; i --) g[i] = (x * f[i - 1] + f[i]) % mod;
	g[0] = f[0];
}
void div(int *f, int *g, int x, int len) {//除一个一次多项式
	g[0] = f[0];
	for(int i = 1; i <= len; i ++) g[i] = (f[i] - x * g[i - 1] % mod + mod) % mod;
}
int f[N][505], fa[N][20], size[N], in[N], dep[N], n, q, L;
int LCA(int x, int y) {
	if(dep[x] < dep[y]) swap(x, y);
	for(int i = 18; i >= 0; i --) if(dep[fa[x][i]] >= dep[y]) x = fa[x][i];
	if(x == y) return x;
	for(int i = 18; i >= 0; i --) if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
	return fa[x][0];
}
int son(int x, int y) {
	for(int i = 18; i >= 0; i --) if(dep[fa[x][i]] > dep[y]) x = fa[x][i];
	return x;
}
void dfs(int u) {
	int cnt = 0;
	f[u][0] = 1; size[u] = 1;
	for(int i = p[u]; i + 1; i = e[i].nxt) {
		int v = e[i].v;
		if(v == fa[u][0]) continue;
		fa[v][0] = u;
		dep[v] = dep[u] + 1;
		dfs(v); size[u] += size[v];
		mul(f[u], f[u], size[v], ++ cnt);
	}
	mul(f[u], f[u], n - size[u], ++ cnt);
}
int a[N], b[N], fac[N], ifac[N];
signed main() {
	init();
	scanf("%lld%lld%lld", &n, &q, &L);
	
	fac[0] = 1;
	for(int i = 1; i <= n; i ++) fac[i] = fac[i - 1] * i % mod;
	for(int i = 0; i <= n; i ++) ifac[i] = qpow(fac[i], mod - 2);
	for(int i = 1; i < n; i ++) {
		int u, v;
		scanf("%lld%lld", &u, &v);
		insert(u, v);
		insert(v, u);
		in[u] ++, in[v] ++;
	}
	fa[1][0] = 1; dep[1] = 1;
	dfs(1);
	for(int j = 1; j <= 18; j ++)
		for(int i = 1; i <= n; i ++)
			fa[i][j] = fa[fa[i][j - 1]][j - 1];
	while(q --) {
		int u, v, k;
		scanf("%lld%lld%lld", &u, &v, &k);
		if(dep[u] < dep[v]) swap(u, v);
		if(LCA(u, v) == v) div(f[v], b, size[son(u, v)], in[v]);
		else div(f[v], b, n - size[v], in[v]);//断子树
		div(f[u], a, n - size[u], in[u]);
		int ans1 = 0, ans2 = 0;
		for(int i = 0; i <= min(k, in[u] - 1); i ++) ans1 += a[i] * ifac[k - i] % mod * fac[k] % mod, ans1 %= mod;//枚举选几个不是在u上的
		for(int i = 0; i <= min(k, in[v] - 1); i ++) ans2 += b[i] * ifac[k - i] % mod * fac[k] % mod, ans2 %= mod;//同理
		
		printf("%lld\n", ans1 * ans2 % mod);//乘起来
	}
	return 0;
}

总结

联赛的知识不会很难,但思维难度肯定是有的,一定要把思维练上去

发布了157 篇原创文章 · 获赞 90 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_38944163/article/details/102763861