qbzt day5 下午

 

农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。

遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。

John想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)

 

发现数据范围很小,可以状压,那么要压什么东西?

在一个格子种了草之后其他的格子都不能种草

用f[i][s]代表前i行的草种完了,且第i行种的草的长相为d的方案数有多少

 

数位dp:

 

给定一个x,有多少个y满足0<=y<x

一位一位给每一个格子填数

从高向低填,这样方便比较大小

状态:f[i][j]表示已经从高位向低位填了i位,j=0或1,如果j=0,表示填的数已经<x,如果j=1,表示目前填的所有数都=x

初始化:f[l+1][1]=1  因为第l位之前都只能填0

转移:枚举下一位填什么

考虑自己更新别人

如果填进去数之后会导致y比x大,显然就不行了,直接不用转移

如果填进去之前j=0,那么之后不管怎么填j都只能=0

如果填进去之前j=1,只有当填的这一位和x的这一位一样的时候,j才会继续是1

f[i-1][j’]+=f[i][j]

 

例:求[l,r]中的数位之和

0~r的数位之和减0~l-1的数位之和

f[i][j]表示数有多少个

g[i][j]表示数字之和是多少

转移:考虑第下一位到底填什么

如果填1的话对答案的贡献是多少?由于上一位填的可能有很多种,所以1做出的贡献应该是f[i][j]*k

 

#include<iostream>

using namespace std;

int solve(int x)
{
    int l=0;
    while (x>0)
    {
        l++;
        z[l] = x%10;
        x/=10;
    }
    memset(f,0,sizeof(f));
    memset(g,0,sizeof(g));
    f[l+1][1]=1;
    g[l+1][1]=0;
    for (int i=l+1;i>=2;i--)
        for (int j=0;j<=1;j++)
            for (int k=0;k<=9;k++)
            {
                if (j==1 && k>z[i-1]) continue;
                int j_;
                if (j==0) j_=0;
                else if (k==z[i-1]) j_=1;
                else j_=0;
                f[i-1][j_] += f[i][j];
                g[i-1][j_] += f[i][j] * k + g[i][j];
            }
    return g[1][0] + g[1][1];
}

int main()
{
    cin >> l >> r;
    cout << solve(r) - solve(l-1) << endl;
    
    return 0;
}

 

例:求在[l,r]中满足十进制位中相邻两个数字之差至少为2的数有多少个

f[i][j][k]表示已经填到第i位,前面是小于还是等于,第i位填的k

在填第i-1位的时候看看第i位填的是多少

windy数

 

例:求在[l,r]中有多少数满足各位数乘积=k

如果按照上个题的方法做,设f[i][j][k],会炸空间

因为k是很多个个位数乘起来的,所以不能出现超过10的质因子,也就是说我们开的大部分数组都是空的

设f[i][j][a][b][c][d],a,b,c,d表示2^a,3^b,5^c,7^d,a,b,c,d分别取log2 10^18 ,log3 10^18 ,log5 10^18 ,log3 10^18

但是直接做时间复杂度还是会炸,继续优化

我们发现这些如果一个数取到上界,其他就取不到上界,枚举就浪费时间了。所以直接dfs预处理出longlong内所有满足这个性质的数,直接把数组改成f[i][j][k],k表示是第k个

 

树形dp

 

顾名思义,在树上进行dp

例:求树上有多少个点(什么沙雕题)

基础思路:从下往上推,在每一个点维护他的子树的信息

f[i]:以i为根的子树有多少点,f[i]就是它所有子树的大小之和+1

 

例:求树的直径(在树上找到两个点使得这两个点的距离最远)

两个点之间的距离一定是一个点到他们的lca再到另一个点

可以看成是lca向下的两条路

也就是说有一个拐点。我们可以维护从每个点向下走最长和次长能走多少

用f[i][0]表示从i向下的最长路,f[i][1]表示次长路

然后枚举每一个点,对f[i][0]+f[i][1]取最大值

那么怎么算?

f[i][0]=max(f[p[j]][0])+1

f[i][1]呢?

如果我们把最长路和次长路混在一起取次长,就有可能经过同一个节点,这样就并不是一条正常的路径了

所以我们再选次长的时候,一定要避免和f[i][0]选到同一个位置去

所以我么直接把剩下的所有儿子的最长取出来取最长

 

#include<iostream>

using namespace std;

void dfs(int i)
{
    for (p is i's son)
        dfs(p);
    
    for (p is i's son)
    {
        int v = f[p][0]+1;
        if (v>f[i][0])
        {
            f[i][1]=f[i][0];
            f[i][0]=v;
        }
        else if (v>f[i][1]) f[i][1]=v;
    }
}

int main()
{
    cin >> n;
    du ru he jian shu;
    
    dfs(1);
    
    return 0;
}

 

例:求树上路径的总长度

f[i]表示以i为根的子树有多少个点,考虑每个边被算进答案多少次

2*f[i]*(n-f[i])对答案的贡献

答案就是对边求一个Σ就完了

 

例:没有上司的舞会

设f[i][0/1]代表以i为根的子树里面选择一些点,0代表没选,1代表选了,在这种情况下获得的最大价值

答案就是max(f[1][0],f[1][1])

如果i选了,他的所有儿子都不能选,f[i][1]=Σf[j][0](j∈son[i]) +a[i]

 

如果i没选,他的儿子可以选也可以不选,f[i][0]=Σmax(f[j][0],f[j][1])(j∈son[i])

 

例:Strategic game

和上个题差不多

 

如果能守护距离不超过2的点?

f[i][0/1/2]表示在i的子树被完全覆盖的情况下往下走到第一个士兵距离是0/1/2

f[i][0]=Σmin(f[j][0],f[j][1],f[j][2])+1(j∈son[i])

g[j][0/1]表示我已经确定了前j个儿子的取值,其中这j个儿子有没有拿出一个0(有士兵)作为答案

 

dp套dp

 

原文地址:https://www.cnblogs.com/lcezych/p/11202698.html