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 m很小,可以直接用KMP匹配,求出那些区间至少要挖掉一个(对于相同的右端点只用保存一个最大的左端点),然后问题就转化成了给出若干区间,然后每个区间至少要挖掉一个,求最小要挖掉多少个
然后发现这个东东可以
D
P
DP
DP
设
f
[
i
]
表
示
一
定
要
取
第
i
个
,
并
且
满
足
前
面
的
要
求
的
方
案
数
,
然
后
转
移
的
话
就
是
设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这段区间里
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,v是独立的, 对于 u u u来说就是求在 u u u的子树中选 k k k个点使得两两点的 L C A LCA LCA是 u u u, 对于 v v v做一个相同的东西,然后乘起来就行了
如果
u
,
v
u, v
u,v是祖孙关系,那不妨设
u
u
u 是
v
v
v 的祖先,那么
u
u
u 的子树就要改为以
v
v
v 的
方向作为根方向前提下的子树(即讲
v
v
v方向的子树砍掉,然后以
u
u
u做根的子树)
发现为了使两两点之间的
L
C
A
LCA
LCA是
u
u
u,
u
u
u的每个子树中最多取一个点,所以可以做一个01背包,表示如果当前子树选就把方案数乘子树大小。
然后放在
u
u
u上的情况直接枚举个数然后算一下组合数就好了(其实就一个fac)
这样做的时间复杂度还是会爆炸
注意到度数 < = 50 <= 50 <=50
考虑背包的过程,其实就是多项式乘法,对于一个子树
v
v
v的转移就相当于是原多项式乘上
(
1
+
s
i
z
e
[
v
]
X
)
(1+ size[v] X)
(1+size[v]X)
X
k
X ^ k
Xk的系数表示的就是选
k
k
k个的方案数
所以对于 u u u的背包可以变成若干个形如 ( 1 + s i z e [ v ] X ) 的 一 次 多 项 式 (1 + size[v]X)的一次多项式 (1+size[v]X)的一次多项式相乘的东西,这个是满足交换律的
可以先把 u u u的每个方向的子树做多项式乘法,乘成一个多项式,然后前面的去掉一颗子树就相当于是除一个形如 ( 1 + s i z e [ v ] X ) (1 + size[v]X) (1+size[v]X)的多项式
乘法和除法都是可以O(L)进行的
最后记得枚举在
u
,
v
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;
}
总结
联赛的知识不会很难,但思维难度肯定是有的,一定要把思维练上去