动态规划题集01

动态规划题集01

1. 涂色paint

  • 题目:有一个长为 n 的字符串为涂色目标,不同字母表示不同颜色,一开始均没有颜色,每次可以选择连续的一段染成同一种颜色,颜色会覆盖。问最少染色次数 ( n ≥ 50 ) (n \ge 50) (n50).

  • 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,r1)}.
    • 如果 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)=lk<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 ab 对应的右括号是否必须在 c ∼ d c\sim d cd 对应的右括号的左边,即 ( 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 2k1 个黑白相间的段,并且个数小于 2 k − 1 2k-1 2k1 的黑白相间的段都能被染出。

▪直接 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 wi1,k8,1n300

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 k1 1 1 1 来讨论.

如果不余1, 从最后一位开始往前每隔 k − 1 k-1 k1 位枚举 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 N1000,1xi,ti10000,0vi10000

首先显然已经获得权值的小球是一个区间,所以可以用 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,r1,0) f ( l , r − 1 , 1 ) f(l,r-1,1) f(l,r1,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 0N 之内的正整数 K K K,你要在这棵树中选择 K K K 个点,将其染成黑色,并
将其他的 N − K N-K NK 个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的收益。问收益最大值是多少。

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}} 1Kleni=1nCi×Kdep[i] 的最大值,其中 l e n len len 为环的大小。 N , m ≤ 60 , 0.3 ≤ k < 1 N , m \le60 , 0.3 \le k < 1 N,m60,0.3k<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=1wR(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=1Ki×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}} 1Kleni=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]=CuK+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]=CuKk+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 N500000

我们用树哈希的方式找到同构的树。树的同构来源于,选定一个树根后,子树的顺序会影响树的长的样子,选用不同树根可能会得到不同的子树. 树哈希就是选定一个树根后,无视子树的顺序而计算出的一个值. 因此一个树的哈希值取决于选择的树根。

而且,一棵树的树根选定后,所有子树的树根也跟着确定了. 因此我们只需要找到树的重心,把这个当作根计算哈希,其所有子树的哈希值也就跟着算出来了.

现在来考虑上树同构。我们一般的判断树同构的方法是以重心作为根,而此处也需要使用重心来作根。注意若有两个重心,需要新建一个点作为重心,连接原来的两个重心。

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+num1,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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值