动态规划题集01
1. 涂色paint
-
题目:有一个长为 n 的字符串为涂色目标,不同字母表示不同颜色,一开始均没有颜色,每次可以选择连续的一段染成同一种颜色,颜色会覆盖。问最少染色次数 ( n ≥ 50 ) (n \ge 50) (n≥50).
-
f ( l , r ) f(l,r) f(l,r) 表示把 [ l , r ] [l,r] [l,r] 染好颜色的最少次数。
- 如果 s [ l ] = s [ r ] s[l]=s[r] s[l]=s[r],那么我们在染好 l l l 的同时不如顺便把 r r r 染上颜色,所以 f ( l , r ) = min { f ( l + 1 , r ) , f ( l , r − 1 ) } f(l,r) = \min\{f(l+1,r),f(l, r - 1)\} f(l,r)=min{f(l+1,r),f(l,r−1)}.
- 如果 s [ l ] ≠ s [ r ] s[l] \ne s[r] s[l]=s[r],可以枚举中转节点, f ( l , r ) = min l ≤ k < r { f ( l , k ) + f ( k + 1 , r ) } f(l,r) = \min\limits_{l \le k < r}\{f(l,k) + f(k + 1,r)\} f(l,r)=l≤k<rmin{f(l,k)+f(k+1,r)}
for(int len = 1; len <= n; len++)
{
for(int l = 1, r = l + len - 1; r <= n; l++, r++)
{
f[l][r] = INF;
if(len == 1)
{
f[l][r] = 1;
continue;
}
if(str[l] == str[r]) f[l][r] = min(f[l + 1][r], f[l][r - 1]);
else for(int k = l; k + 1 <= r; k++)
{
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r]);
}
}
}
2. BZOJ4350: 括号序列再战猪猪侠
题目:有一个长为 2*n (n<=300) 的未知的合法括号序列,给出若干对信息 ( a i , b i ) (a_i,b_i) (ai,bi),表示第 a i a_i ai 个左括号对应的右括号在第 b i b_i bi 个左括号对应的右括号的左边。询问括号序列的方案数,对质数取模。
设 f ( l , r ) f(l,r) f(l,r) 是第 l l l 个左括号到第 r r r 个左括号的合法排列的数量。
考虑对第 l l l 个左括号对应的右括号的位置分类讨论:(…),(…)…
前者需要满足: l l l 对应的右括号可在 l + 1 l+1 l+1 到 r r r 对应的右括号右边。后者需要满足: l l l 对应的右括号可在 l + 1 l+1 l+1 到 k k k 对应的右括号右边, k + 1 k+1 k+1 到 r r r 对应的右括号均可在 l l l 到 k k k 对应的右括号右边。
如何快速判断上面的条件?比如我们想知道 a ∼ b a\sim b a∼b 对应的右括号是否必须在 c ∼ d c\sim d c∼d 对应的右括号的左边,即 ( a , c ) ∼ ( b , d ) (a,c) \sim (b,d) (a,c)∼(b,d) 矩阵应该全为 1 1 1. 这个用二维前缀和即可.
无解的情况,我们可以这么考虑:把这个括号串看作一个多叉树,左括号就是向下走(开新的子节点),右括号就是向上走(回溯到父结点)。输入 a , b a,b a,b,若 a < b a < b a<b 则说明 a a a 会在 b b b 出现前先走完子结点然后回到 a a a. 若 a > b a > b a>b 说明 b b b 是 b b b 是 a a a 的祖先. 如果出现 a > b a > b a>b 且 b > a b > a b>a 的情况就会无解.
for(ll len=2;len<=n;++len)
{
for(ll l=1;l<=n-len+1;++l){
ll r=l+len-1;
if(!sum(l,l,l+1,r)) f[l][r]=(f[l][r]+f[l+1][r])%mod;
if(!sum(l+1,r,l,l)) f[l][r]=(f[l][r]+f[l+1][r])%mod;
for(ll k=l;k<=r;++k)
if(!sum(l,l+1,l,k) && !sum(k+1,l,r,k))
f[l][r]=(f[l][r]+f[l+1][k]*f[k+1][r])%mod;
}
}
3. 作业
题目:给一个长度为 N 的序列,序列中每个元素初始为无色,每个元素有一个目标颜色(黑或白),你有 K 次机会选择一个区间染色,注意颜色会覆盖。问最多能让多少个元素满足目标颜色。
N < = 100000 , K < = 50 N <= 100000 , K <= 50 N<=100000,K<=50
注意染到最后一定是黑白相间的。而最多形成 2 k − 1 2k-1 2k−1 个黑白相间的段,并且个数小于 2 k − 1 2k-1 2k−1 的黑白相间的段都能被染出。
▪直接 DP, d p [ i ] [ j ] [ 0 / 1 ] dp[i][j][0/1] dp[i][j][0/1] 表示染到第 i i i 个,已经有了 j j j 段,最后一段是 白/黑色,最多的满足目标颜色的元素个数。
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= 2 * k - 1; j++)
{
f[i][j][0] = max(f[i - 1][j - 1][1], f[i - 1][j][0]) + (a[i] == 0);
f[i][j][1] = max(f[i - 1][j - 1][0], f[i - 1][j][1]) + (a[i] == 1);
}
}
int ans = 0;
for(int j = 1; j <= 2 * k - 1; j++) ans = max(ans, max(f[n][j][0], f[n][j][1]));
4. 字符合并
题目:有一个长度为 n n n 的 01 01 01 串,每次可以将相邻的 k k k 个数字合并,得到一个新的数字,并获得一定分数 。输入将给出 2 k 2^k 2k 条信息,表示所有可能的合并情况下的合并得到的数字 c i c_i ci,以及得到的分数 w i w_i wi。求出你能获得的最大分数。 c i c_i ci 为 0 0 0 或 1 1 1, w i ≥ 1 , k ≤ 8 , 1 ≤ n ≤ 300 w_i\ge 1,k\le 8,1\le n\le 300 wi≥1,k≤8,1≤n≤300
设 f ( l , r , s t a ) f(l,r,sta) f(l,r,sta) 表示把 [ l , r ] [l,r] [l,r] 变为状态 s t a sta sta 获得的最大分数。考虑状态转移起点 f ( i , i , a i ) = 0 f(i,i,a_i) = 0 f(i,i,ai)=0,其他的初始化为 − ∞ -\infty −∞.
转移的时候,按照区间长度是否模 k − 1 k-1 k−1 余 1 1 1 来讨论.
如果不余1, 从最后一位开始往前每隔 k − 1 k-1 k−1 位枚举 f [ l , r , s t a ] = f [ l , d , s t a > > 1 ] + f [ d + 1 , r , s t a & 1 ] f[l,r,sta] = f[l,d,sta >> 1] + f[d+1,r,sta\ \&\ 1] f[l,r,sta]=f[l,d,sta>>1]+f[d+1,r,sta & 1] . 即枚举最后一位是从那一部分转移过来的. 到达不了的状态肯定还是 − ∞ -\infty −∞,到达得了但是位数小于 k k k 位无法合并的话,其状态会被更新为 0 0 0.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 310, M = (1 << 8) + 10;
const ll INF = 0x3f3f3f3f3f3f3f3fll;
ll f[N][N][M];
int a[N], w[M], c[M];
int n, k;
char s[N];
int main()
{
scanf("%d%d", &n, &k);
scanf("%s", s + 1);
for(int i = 1; i <= n; i++) a[i] = s[i] - '0';
for(int i = 0; i < (1 << k); i++) scanf("%d%d", &c[i], &w[i]);
memset(f, -0x3f, sizeof f);
for(int i = 1; i <= n; i++) f[i][i][a[i]] = 0;
for(int len = 2; len <= n; len++)
{
int m = (len - 1) % (k - 1);
for(int l = 1, r = l + len - 1; r <= n; l++, r++)
{
for(int d = r - 1; d >= l; d -= k - 1)
{
if(!m)
{
for(int u = 0; u < (1 << (k - 1)); u++)
{
f[l][r][c[u << 1]] = max(f[l][r][c[u << 1]],
f[l][d][u] + f[d + 1][r][0] + w[u << 1]);
f[l][r][c[(u << 1) | 1]] = max(f[l][r][c[(u << 1) | 1]],
f[l][d][u] + f[d + 1][r][1] + w[(u << 1) | 1]);
}
}
else
{
for(int u = 0; u < (1 << k); u++)
{
f[l][r][u] = max(f[l][r][u], f[l][d][u >> 1] + f[d + 1][r][u & 1]);
}
}
}
}
}
ll res = -INF;
for(int S = 0; S < (1 << k); S++)
{
res = max(res, f[1][n][S]);
}
printf("%lld\n", res);
}
5. Sue的小球
题目:在 x x x 轴上有 N 个坐标为 ( x i , 0 ) (x_i,0) (xi,0) 的小球 ,每个小球有一个权值 ti ,每过一个单位时间权值会减少 vi,初始时你的坐标为 (0,0),移动一个单位距离需要花费一个单位时间。到一个小球时可以选择获得这个小球此时的权值(以后经过这个小球不会再获得权值),问获得所有小球时权值和最大是多少。
N ≤ 1000 , 1 ≤ ∣ x i ∣ , ∣ t i ∣ ≤ 10000 , 0 ≤ v i ≤ 10000 N\le 1000 , 1 \le |x_i|, |t_i| \le 10000 , 0 \le v_i \le 10000 N≤1000,1≤∣xi∣,∣ti∣≤10000,0≤vi≤10000
首先显然已经获得权值的小球是一个区间,所以可以用 d p [ i ] [ j ] [ 0 / 1 ] dp[i][j][0/1] dp[i][j][0/1] 定义为处理第 i 个小球到第 j 个小球后的状态。
考虑到一个小球被获得时的权值并不能很容易地被计算,而每个小球都必须获得,那么就假设我们都先获得了,然后转移时减去此时减少的权值即可。
可以分析出来 f ( l , r , 0 ) f(l,r,0) f(l,r,0) 可以从 f ( l + 1 , r , 0 ) f(l+1,r,0) f(l+1,r,0) 与 f ( l + 1 , r , 1 ) f(l+1,r,1) f(l+1,r,1) 转移过来. f ( l , r , 1 ) f(l,r,1) f(l,r,1) 可以从 f ( l , r − 1 , 0 ) f(l,r - 1,0) f(l,r−1,0) 与 f ( l , r − 1 , 1 ) f(l,r-1,1) f(l,r−1,1).
状态转移的时候,注意减去的值是当前没有获得的小球乘移动时间/移动距离. 不过要注意初始化 f ( i , i , 0 / 1 ) f(i,i,0/1) f(i,i,0/1) 的时候不要忘记减去所有的小球而不是尚未获得的小球.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1010;
struct P
{
ll x, y, v;
bool operator < (const P& p) const
{
return x < p.x;
}
}balls[N];
int n;
ll f[N][N][2], x0, cnt[N][N][2], S[N];
ll get(int l, int r)
{
return S[n] - (S[r] - S[l - 1]);
}
int main()
{
scanf("%d%lld", &n, &x0);
ll sum = 0;
for(int i = 1; i <= n; i++) scanf("%lld", &balls[i].x);
for(int i = 1; i <= n; i++)
{
scanf("%lld", &balls[i].y);
sum += balls[i].y;
}
for(int i = 1; i <= n; i++)
{
scanf("%lld", &balls[i].v);
}
sort(balls + 1, balls + n + 1);
for(int i = 1; i <= n; i++)
{
S[i] = S[i - 1] + balls[i].v;
}
for(int i = 1; i <= n; i++)
{
f[i][i][0] = f[i][i][1] = sum - abs(balls[i].x - x0) * S[n];
}
for(int len = 2; len <= n; len++)
{
for(int l = 1, r = l + len - 1; r <= n; l++, r++)
{
f[l][r][0] = max(f[l + 1][r][0] - (balls[l + 1].x - balls[l].x) * get(l + 1, r),
f[l + 1][r][1] - (balls[r].x - balls[l].x) * get(l + 1, r));
f[l][r][1] = max(f[l][r - 1][1] - (balls[r].x - balls[r - 1].x) * get(l, r - 1),
f[l][r - 1][0] - (balls[r].x - balls[l].x) * get(l, r - 1));
}
}
double ans = double(max(f[1][n][0], f[1][n][1]) / 1000.0);
printf("%.3f\n", ans);
}
6. 树上染色
有一棵点数为
N
N
N 的树,树边有边权。给你一个在
0
∼
N
0\sim N
0∼N 之内的正整数
K
K
K,你要在这棵树中选择
K
K
K 个点,将其染成黑色,并
将其他的
N
−
K
N-K
N−K 个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的收益。问收益最大值是多少。
用 f [ i ] [ j ] f[i][j] f[i][j] 表示在 i i i 的子树中选择 j j j 个黑点所能得到的最大收益(只考虑在 i i i 子树中的边的贡献)。转移时要用子树内的黑(白)点 × \times × 子树外的黑(白)点个数 × \times × 边权,将贡献加到 f f f 值上。
需要注意的是,这个题没必要 u u u 染成黑色还是白色分类讨论. 直接枚举子树中有多少黑点,然后把 ( u , v ) (u,v) (u,v) 这条边的贡献加上就可以了.
#include<bits/stdc++.h>
using namespace std;
const int N = 2010, M = N * 2;
typedef long long ll;
const ll INF = 0x3f3f3f3f3f3f3f3fll;
ll f[N][N], w[M];
int h[N], e[M], ne[M], idx, sz[N];
inline void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int n, v;
int dfs(int u, int fa)
{
f[u][0] = f[u][1] = 0;
sz[u] = 1;
for(int i = h[u]; i != -1; i = ne[i])
{
int son = e[i];
if(son == fa) continue;
sz[u] += dfs(son, u);
for(int j = min(sz[u], v); j >= 0; j--)
{
for(int k = 0; k <= min(sz[son], j); k++)
{
//不可以从大到小枚举. 主要是出在 f[u][0] 这个地方. y
ll t = w[i] * (k * (v - k) + (sz[son] - k) * (n - v - (sz[son] - k)));
f[u][j] = max(f[u][j], f[u][j - k] + f[son][k] + t);
//如果 f[u][j - k] 为负无穷,说明仅凭左边的子树无法凑出 j - k 个黑点
//那么 f(u, j - k) 是一个不合法状态.
}
}
}
return sz[u];
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d%d", &n, &v);
for(int i = 1; i < n; i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
memset(f, -0x3f, sizeof f);
dfs(1, -1);
printf("%lld\n", f[1][v]);
}
7. 奥运物流
给出一个 N N N 个点的基环内向树,即每个点均有一个后继,保证 1 1 1 号点一定在环上,且所有点都能走到 1 1 1 号点。给出一个常系数 k k k,每个点有一个系数 C i C_i Ci,你可以修改不超过 m m m 个点的后继, 1 1 1 号结点的后继不能被更改,问 ∑ i = 1 n C i × K d e p [ i ] 1 − K l e n \frac{\sum_{i=1}^nC_i\times K^{dep[i]}}{1-K^{len}} 1−Klen∑i=1nCi×Kdep[i] 的最大值,其中 l e n len len 为环的大小。 N , m ≤ 60 , 0.3 ≤ k < 1 N , m \le60 , 0.3 \le k < 1 N,m≤60,0.3≤k<1
原本题面是:对于基站 i i i ,我们定义其“可靠性” R ( i ) R(i) R(i) 如下:设物流基站 i i i 有 w w w 个前驱基站 P 1 , P 2 , … P w P_1,P_2, … P_w P1,P2,…Pw ,即这些基站以 i i i 为后继基站,则基站 i i i 的可靠性 R ( i ) R(i) R(i) 满足 : R ( i ) = C i + k ∑ j = 1 w R ( P j ) R(i) = C_i + k \sum\limits_{j=1}^w R(P_j) R(i)=Ci+kj=1∑wR(Pj).
现在求
R
(
1
)
R(1)
R(1) 最大值. 先不考虑环的情况,于是变成了一棵树。这样子我们答案的贡献是
∑
i
=
1
n
C
i
×
k
d
e
p
[
i
]
\sum_{i=1}^nC_i\times k^{dep[i]}
∑i=1nCi×kdep[i]
其中
d
e
p
dep
dep 是点的深度考虑环的影响,显然是R(1)的贡献沿着环反复加在环上,假设环的长度为
l
e
n
len
len. 那么,答案就是
R
(
1
)
∑
i
=
1
∞
K
i
×
l
e
n
R(1)\sum_{i=1}^{\infty}K^{i\times len}
R(1)∑i=1∞Ki×len,等比数列求和一下,所以答案就是那个等式化简后就是
∑
i
=
1
n
C
i
×
K
d
e
p
[
i
]
1
−
K
l
e
n
\frac{\sum_{i=1}^nC_i\times K^{dep[i]}}{1-K^{len}}
1−Klen∑i=1nCi×Kdep[i]
既然每次修改的结果一定是将后继修改为
1
1
1
那么,我们可以考虑一下
d
p
dp
dp
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示
i
i
i的子树中有
j
j
j个点的后继修改为
1
1
1的子树最大贡献
但是这样子我们似乎没法将i这个点的贡献转移给
1
1
1
因为我们并不知道
i
i
i的深度。
所以我们增加一维
f
[
i
]
[
j
]
[
k
]
f[i][j][k]
f[i][j][k]表示以i为根的子树中,
j
j
j个点的后继修改为
i
i
i,
其中i号点到
1
1
1的距离是
k
k
k的最大贡献
所以我们的转移有两种,一种是将i号点的后继修改为
1
1
1号点
那么,它的所有儿子的深度要么是
1
1
1,也就是后继修改为了
1
1
1
要么是
2
2
2,也就是没有修改后继
转移就是
f
[
u
]
[
i
]
[
k
]
=
C
u
⋅
K
+
∑
v
m
a
x
(
f
[
v
]
[
j
]
[
1
]
,
f
[
v
]
[
j
]
[
2
]
)
f[u][i][k]=Cu⋅K+∑vmax(f[v][j][1],f[v][j][2])
f[u][i][k]=Cu⋅K+∑vmax(f[v][j][1],f[v][j][2])
对于第二维是一个背包问题
如果不修改当前点的后继的话,它的所有儿子的深度要么是当前点加一,要么是
1
1
1
f
[
u
]
[
i
]
[
k
]
=
C
u
⋅
K
k
+
∑
v
m
a
x
(
f
[
v
]
[
j
]
[
k
+
1
]
,
f
[
v
]
[
j
]
[
1
]
)
f[u][i][k]=C_u·K^k+\sum_vmax(f[v][j][k+1],f[v][j][1])
f[u][i][k]=Cu⋅Kk+∑vmax(f[v][j][k+1],f[v][j][1])
8. 粉刷匠
有N条木板,每条木板有 M 个格子。每个格子要被刷成红色或蓝色。 每次粉刷只能选择一条木板上一段连续的格子,然后涂上一种颜色。 每个格子最多只能被粉刷一次。 一共只能粉刷 T 次,问最多能正确粉刷多少格子?(1<=N,M<=50, T<=2500)
注意这个题是一个格子只能涂一次,很简单,分别处理每个木板,然后对每个木板跑一个分组背包即可.
#include<bits/stdc++.h>
using namespace std;
const int N = 60, T = 2510;
int f[N][T][2], dp[N][T];
char s[N];
int n, m, t;
int main()
{
scanf("%d%d%d",&n, &m, &t);
for(int u = 1; u <= n; u++)
{
scanf("%s", s + 1);
for(int i = 1; i <= m; i++)
{
for(int j = 1; j <= t; j++)
{
f[i][j][0] = max(f[i - 1][j][0], f[i - 1][j - 1][1]) + (s[i] == '0');
f[i][j][1] = max(f[i - 1][j][1], f[i - 1][j - 1][0]) + (s[i] == '1');
}
}
for(int j = 1; j <= t; j++)
{
dp[u][j] = dp[u - 1][j];
for(int k = 1; k <= j; k++)
{
dp[u][j] = max(dp[u][j], dp[u - 1][j - k] + max(f[m][k][0], f[m][k][1]));
}
}
}
printf("%d\n", dp[n][t]);
return 0;
}
9. 独钓寒江雪
题目:给一棵 N 个点的无根树,将一些点染黑,相邻点不能均被染黑,问本质不同的方案数。两个方案本质相同,当且仅当存在一种标号方法将一个方案中的点重新标号后,与另一个方案完全相同。
N ≤ 500000 N \le 500000 N≤500000
我们用树哈希的方式找到同构的树。树的同构来源于,选定一个树根后,子树的顺序会影响树的长的样子,选用不同树根可能会得到不同的子树. 树哈希就是选定一个树根后,无视子树的顺序而计算出的一个值. 因此一个树的哈希值取决于选择的树根。
而且,一棵树的树根选定后,所有子树的树根也跟着确定了. 因此我们只需要找到树的重心,把这个当作根计算哈希,其所有子树的哈希值也就跟着算出来了.
现在来考虑上树同构。我们一般的判断树同构的方法是以重心作为根,而此处也需要使用重心来作根。注意若有两个重心,需要新建一个点作为重心,连接原来的两个重心。
用 f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1] 表示在以 i i i 为根的子树中, i i i 不选或选时的本质不同的方案数
考虑转移,我们需要将 i i i 的子树中同构的子树放到一起来转移。假设某一形态的子树有 n u m num num 个,并且这种子树的 f [ ] [ 0 ] = a , f [ ] [ 1 ] = b f[][0]=a,f[][1]=b f[][0]=a,f[][1]=b。也就是说,对于每一个这种子树,有 a + b a+b a+b 种选择。
那么考虑这个形态的子树对 f [ i ] [ 0 ] f[i][0] f[i][0] 的影响。 a + b a+b a+b 种选择相当于有 a + b a+b a+b 种物品,每种物品没有限制, 求选出 n u m num num 个物品的方案数
比较经典的组合数学问题,可以看作总共有 a + b a + b a+b 个物品,有 n u m num num 个隔板,每个隔板左边的数字表示这个子树要选择的物品,当然允许隔板之间为空,即不定方程 x 1 + x 2 + . . . + x a + b = n u m x_1+x_2+...+x_{a+b} = num x1+x2+...+xa+b=num 的非负整数解的数量. 这个的方案数为 C ( a + b + n u m − 1 , n u m ) C(a+b+num-1,num) C(a+b+num−1,num),也就是说 f [ i ] [ 0 ] f[i][0] f[i][0] 需要累乘上这个方案数。 f [ i ] [ 1 ] f[i][1] f[i][1] 同理,只是选择数变为 a a a 种.
最后答案要讨论一下这个树的重心是一个还是两个。如果是两个的话,两个重心形成的子树哈希值是否一样.
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
const int N = 500010, M = N * 2;
typedef long long ll;
const ll mod = 1e9 + 7;
int h[N], e[M], ne[M], idx;
int n;
inline void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
ll mod_pow(ll x, ll n)
{
ll res = 1;
while(n)
{
if(n & 1) res = res * x % mod;
x = x * x % mod;
n >>= 1;
}
return res;
}
int centroid[2], sz[N], weight[N];
void getCentroid(int u, int fa)
{
sz[u] = 1, weight[u] = 0;
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(v == fa) continue;
getCentroid(v, u);
sz[u] += sz[v];
weight[u] = max(weight[u], sz[v]);
}
weight[u] = max(weight[u], n - sz[u]);
if(weight[u] <= n / 2)
{
centroid[centroid[0] != 0] = u;
}
}
ll rd[N], Hash[N], ans;
mt19937 rnd(0);
void getHash(int u, int fa)
{
sz[u] = Hash[u] = 1;
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(v == fa) continue;
getHash(v, u);
sz[u] += sz[v];
}
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(v == fa) continue;
Hash[u] = Hash[u] * (rd[sz[u]] + Hash[v]) % mod;
}
}
ll f[N][2], fact[N], infact[N];
inline ll add(ll a, ll b)
{
return (a + b) % mod;
}
inline ll mul(ll a, ll b)
{
return a * b % mod;
}
ll C(ll a, ll b)
{
b = min(b, a - b);
ll res = 1;
for(ll i = 0; i < b; i++) res = res * (a - i) % mod;
return res * infact[b] % mod;
}
void dfs(int u, int fa)
{
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(v == fa) continue;
dfs(v, u);
}
unordered_map<ll, int> ID, CNT;
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(v == fa) continue;
ID[Hash[v]] = v, CNT[Hash[v]]++;
}
f[u][0] = f[u][1] = 1;
for(auto p : ID)
{
int v = p.y, cnt = CNT[p.x];
ll a = f[v][0], b = f[v][1];
f[u][0] = mul(f[u][0], C(a + b + cnt - 1, cnt));
f[u][1] = mul(f[u][1], C(a + cnt - 1, cnt));
}
}
void solve()
{
getCentroid(1, -1);
int rt0 = centroid[0], rt1 = centroid[1];
if(!rt1)
{
getHash(rt0, -1);
dfs(rt0, -1);
ans = (f[rt0][0] + f[rt0][1]) % mod;
}
else
{
getHash(rt0, rt1), getHash(rt1, rt0);
dfs(rt0, rt1), dfs(rt1, rt0);
if(Hash[rt0] == Hash[rt1])
{
ans = C(f[rt0][0] + 1, 2);
ans = add(ans, mul(f[rt0][0], f[rt0][1]));
}
else
{
ans = mul(f[rt0][0], f[rt1][0]);
ans = add(ans, mul(f[rt0][0], f[rt1][1]));
ans = add(ans, mul(f[rt0][1], f[rt1][0]));
}
}
}
void pre(int n)
{
fact[0] = infact[0] = 1;
for(ll i = 1; i <= n; i++)
{
rd[i] = rnd() % mod;
fact[i] = i * fact[i - 1] % mod;
}
infact[n] = mod_pow(fact[n], mod - 2);
for(ll i = n - 1; i >= 1; i--)
{
infact[i] = infact[i + 1] * (i + 1) % mod;
}
}
int main()
{
memset(h, -1, sizeof h);
pre(N - 1);
scanf("%d", &n);
for(int i = 1; i < n; i++)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
solve();
printf("%lld\n", ans);
return 0;
}
/*
1
5
1 2
1 3
1 4
1 5
6
1 2
1 3
1 4
4 5
4 6
*/
10. SDOI2013刺客信条
题目:给你两棵形态相同的 N 个点的树,树的每个结点可能是黑色或白色。问在第一棵树中最少修改几个点的颜色,可以使得第一棵树在重新修改标号后与第二棵树完全相同。 N < = 700 N <= 700 N<=700
首先,以重心为根,若有两个重心则新建一个点作为重心。此时由于两棵树形态相同,不妨先统一点的标号。
用 f [ i ] [ j ] f[i][j] f[i][j] 表示让第一棵树中以 i i i 为根的子树与第二棵树中以 j j j 为根的子树修改标号后完全相同,最少需要修改颜色的点数。注意此时以 i i i 和 j j j 为根的子树必须同构
转移相当于将 i 的子树与 j 的子树进行一一匹配,即求最小费用的完备匹配,用 KM 或者费用流来做即可。
答案即为 f [ r o o t ] [ r o o t ] f[root][root] f[root][root]
11. 有限背包计数问题
你有一个大小为 N N N 的背包,你有 N N N 种物品,第 i i i 种物品的大小为 i i i,且有 i i i 个,求装满这个背包的方案数有多少。两种方案不同当且仅当存在至少一个数 i i i 满足第 i i i 种物品使用的数量不同。答案对 23333333 23333333 23333333 取模。 1 < = N < = 100000 1 <= N <= 100000 1<=N<=100000