• 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

     

  • 相关阅读:
    匿名对象、栈空间和堆空间,String的两种实例化方式的比较,"=="和"equals()"的区别
    间接调用父类私有成员方法、通过抽象类可以达到不需要实现接口所有方法的目的
    this关键字、构造快和静态块
    java可变参数的支持和foreach输出
    java-循环结构体
    switch和if else的区别和应用
    作用域对象
    JavaWeb1
    JavaScript 表单编程
    好久不见
  • 原文地址:https://www.cnblogs.com/lcezych/p/11202698.html
Copyright © 2020-2023  润新知