zoukankan      html  css  js  c++  java
  • 五一 DAY 3

     DAY 3      2019.4.30


    动态规划DP

    Dp是一个很抽象的东西

    方法没有明显区别,很难总结套路

    啥是DP?

    DP等价于DAG!!!

    (1)无后效性:DP的所有状态之间组成一个DAG

    (2)最优子序列

    (3)阶段性

    (4)转移方程:如何计算状态

                              一般是顺序转移

                              有时候乱序,因为DP 是DAG,可以拓扑排序,然后从头for一遍

    (5)状态:题目要算的东西

    前置知识

    简单的例子

    斐波那契数列 

    0   1  1  2  3  5  8  13 

             F0=0          //边界条件

             F1=1          //边界条件

       Fn=Fn-1+Fn-2     // 转移方程    

    定义域,f0到正无穷

    数列有一个边界条件,F0,F1      

    规定好的条件叫边界条件f0 , f1 , 不依赖于其他feibo数的值,直接求出值

    f0,f1,.....fn 叫做状态,dp要求的未知东西,根据边界条件求出,依赖转移方程

    对于DP,确定边界条件,转移方程,状态

    F0,F1,.....Fn     //状态

    F0=0          //边界条件

    F1=1          //边界条件

    Fn=Fn-1+Fn-2     // 转移方程

     

     

    (我们来看这个题)

    以斐波那契数列为例,求斐波那契数列第n项 

    数组f[n]  表示 feibo第n 项

          其他值加起来求 f[a] ,

          用到再算,别人更新自己

    #include<cstdio>
    #include<iostream>
    
    using namespace std;
    
    int n,f[233];
    
    int main()
    {
        cin >> n;
        f[0]=0;
        f[1]=1;
        
        for (int a=2;a<=n;a++)
        {
            f[a]=f[a-1]+f[a-2];
        }
        
        cout << f[n] << endl;
        
    }

     

    假设 f[a] 已经求出,考虑 f[a] 会影响到哪个值

    自己更新别人

    #include<cstdio>
    #include<iostream>
    
    using namespace std;
    
    int n,f[233];
    
    int main()
    {
        cin >> n;
        f[0]=0;
        f[1]=1;
        for (int a=0;a<n;a++)
        {
            f[a+1] += f[a];
            f[a+2] += f[a];    
        }
        
        cout << f[n] << endl;
    }

     

    Dfs搜索求出feibo

    O(f[n]) 代码复杂度和feibo第n项大小一样

    斐波那契数列通项公式

    后项小于一

    O与Feibo前项有关,指数级别复杂度,太慢

    斐波那数列的第n项接近2^n

    慢的原因:重复调用,每次产生一个新数都要多次计算已经算过的项

    记忆化搜索:

    记忆化可以解决的,递推或递归一定可以解决

    记忆化搜索要多开一个数组,判断是否搜索过,空间多了一点

    (矩阵加速实现logn)

    保证每一项只被算一次, O(n)

    #include<iostream>
    
    using namespace std;
    
    int f[n];
    
    bool suan_le_mei[n];   //判断是否算过 
    
    int dfs(int n)
    {
        if (n==0) return 0;       
        if (n==1) return 1;
    if (suan_le_mei[n]) return f[n]; //如果已经算过了,那就不算了,直接返回这个值 suan_le_mei[n] = true; //否则标记已算过 f[n] = dfs(n-1) + dfs(n-2); //计算 return f[n]; }// O(n) int main() { cin >> n; cout << dfs(n) << endl; return 0; }

     

    介绍4个有套路的

     


     

     

     

    显然   Ans= r-l+1

     

    考虑用数位DP

     我们设 [ l , r ] 表示区间 l 到 r 中有多少个数字  (前缀和)

    首先问题就有一步转化

      [ l , r ] = [ 0 , r ] - [ 0 , l - 1 ]

    那么问题的关键就在于求 [ 0 , x ] 

    十进制表示 x ,那么它的各位数字分别为

         xn      xn-1      xn-2    .....       x0

    从最高位一位一位求[0,x]  (区间有多少个数字)

    等价于找到v,使得0≤v≤x

    求v的个数

    十进制表示 v ,那么它的各位数字分别为

       vn     Vn-1      Vn-2    .....       V0

     V可能有前导0,但任然满足0≤v≤x

         xn      xn-1      xn-2    .....       x0

         vn     Vn-1      Vn-2    .....       V0

    在第二行的每一位填上1-9的数,保证0≤v≤x

    求v的方案数

    填数:从高位开始,从n+1位置转移到n位

    那么填Vn-3的时候, vn     Vn-1      Vn-2 已经填

    保证 vn     Vn-1      Vn-2 <=  xn      xn-1      xn-2  (v前三位 ≤  x前三位)

    希望填了Vn-3之后仍然满足0≤v≤x,前四位≤前四位

    分类讨论:

    (1) vn     Vn-1      Vn-2   <  xn      xn-1      xn-2  

              此时 Vn-3 填 0--9 任意数

    (2) vn     Vn-1      Vn-2   =  xn      xn-1      xn-2  

             此时 Vn-3 填 0-- xn-3  任意数

     f [ i ] [ j ] 由 f [ i - 1 ] [ j ’ ] 确定

    先解释一下处理前导0

         0   xn      xn-1      xn-2    .....       x0

         0   vn     Vn-1      Vn-2    .....       V0

    填完n+1位后,X,V,相等的方案数有1种,f[n+1][1] = 1;

    #include<iostream>
    
    using namespace std;
    
    int l,r,z[233];    //存储每一位 
    
    int f[23333][2];
    
    int solve(int x)   //solve(i),表示0到i有多少个数
    {
        int n=0;
        while (x)
        {
            z[n] = x%10;
            x/=10;
            n++;
        }
        n--;
        
        memset(f,0,sizeof(f));   //由于要两次DP,所以开始前清空数组
    
        f[n+1][1] = 1;     //边界条件,处理前导零的状态
    
        for (int a=n;a>=0;a--)   // 从头开始填
            for (int b=0;b<=1;b++)  //分类讨论
            {
                if (b==0)
                {
                    for (int c=0;c<=9;c++)
                        f[a][0] += f[a+1][b]; //保证始终v<x,方案数直接传递,
                                              //c可以有很多选择,每次成立一次就加一遍上个状态
                }
                else
                {
                    for (int c=0;c<=z[a];c++)
                    {
                        if (c==z[a])  //填a位的时候方案就转移到了f[a][0]
                                      //因为c可以填好几个数,每填一次就+=上一次的状态
                            f[a][1] += f[a+1][b];
                            
                        else 
                            f[a][0] += f[a+1][b];
                    }
                }
            }
            
        return f[0][0] + f[0][1];    // 各位填好后v<=x的方案总数
    }
    
    int main()
    {
        cin >> l >> r;
        
        cout << solve(r) - solve(l-1) << endl;
        
        return 0;
    }

          G[ ][ ]表示数位之和

    #include<iostream>
    
    using namespace std;
    
    int l,r,z[233];    //存储每一位 
    
    int f[23333][2];
    int g[23333][2];
    
    int solve(int x)   //solve(i),表示0到i有多少个数
    {
        int n=0;
        while (x)
        {
            z[n] = x%10;
            x/=10;
            n++;
        }
        n--;
        
        memset(f,0,sizeof(f));   //由于要两次DP,所以开始前清空数组
        memset(g,0,sizeof(g));
        
        f[n+1][1] = 1;     //边界条件,处理前导零的状态
        g[n+1][1] = 0;
        
        for (int a=n;a>=0;a--)        // 从头开始填
            for (int b=0;b<=1;b++)   //分类讨论 
            {
                if (b==0)
                {
                    for (int c=0;c<=9;c++)
                    {
                        f[a][0] += f[a+1][b];
                        g[a][0] += g[a+1][b] + f[a+1][b] * c;   //拆分数字之和,a+1位和a位(每个方案数贡献一个)
                    }
                }
                else
                {
                    for (int c=0;c<=z[a];c++)    
                    {
                        if (c==z[a]) 
                        {
                            f[a][1] += f[a+1][b];
                            g[a][1] += g[a+1][b] + f[a+1][b] * c;
                        }
                        else 
                        {
                            f[a][0] += f[a+1][b];
                            g[a][0] += g[a+1][b] + f[a+1][b] * c;
                        }
                    }
                }
            }
            
        return g[0][0] + g[0][1];  //填完个位之后,统计所有 v <= x 的数位之和
    }
    
    int main()
    {
        cin >> l >> r;
        
        cout << solve(r) - solve(l-1) << endl;
        
        return 0;
    }

    比如:135 合法   123非法

    题目中有几个条件,就可以列一个多少维度的状态

    这个题比之前多了一个条件,多一维状态

     

     推荐题目:

    洛谷 Windy数

    纠正:

    十年以前的省选题,到今天只是NOIP难度

    上岁数的省选题不一定是省选难度

    k可以填0---9

    由于k!=11

    所以可以质因数分解

     k = 2* 3* 5* 7d

    so,可以把K拆成四个维度,降低复杂度

    复杂度   a+b+c+d  : log21018+ log31018+ log51018+ log101018


     

    显然是n,

    考虑DP

    f[i]表示以i为根的子树中有多少个点

    边界条件 f[leaf]=1

    树形dp 由下至上一点点算上来

    DFS 实现节点i子树有多少个点

     

    对于每个点,枚举所有儿子,加起来子树之和,再加上自己的1个点

    转移方程:

    f[i]=f[son1]+f[son2]+.....f[sonn]+1

    伪代码如下:

    #include<iostream>
    
    using namespace std;
    
    int f[233];
    
    void read_tree()    //存图 
    
    void dfs(int p)      //DFS 实现节点i子树有多少个点
    {
        for (x is p's son)
        {
            dfs(x);
            f[p] += f[x];
        }
        f[p] ++;
    }
    
    int main()
    {
        cin >> n;
        read_tree();
        
        dfs(1);
        cout << f[1] << endl;
        
        return 0;
    }

     

    直径:在树上找到两个点使得他们距离最远,这个距离就叫做直径

     直径为三

    提示:考虑一棵子树内部直径如何求

     

    从一个点向下走两条路,合起来这两条路,就是一条路径

    要找:

    从一个点向下走最长的路径,和次长路径,合起来,对应这个拐点的最大路径

    f[i][0]  i向下最长路长度

    f[i][1]  i向下次长路长度

    举例

    最长路f[p][0]=max( f[p1][0] , f[p2][0] ....  f[pn][0] )+1

    次长路

    f[p][1]=max( f[p1][0] , f[p2][1] ....  f[pn][0] )+1

    这样就错了,会出现

     

    在找最长路的时候经过了p2,次长路的时候重复了p2,显然这是不可以的

    所以直接删掉p2就完了

    f[p][1]=max{ f[p1][0] , f[p3][0] ....  f[pn][0] } +1

    得到:

    最长路      f[p][0]=max( f[p1][0] , f[p2][0] ....  f[pn][0] )+1

    次长路      f[p][1]=max{ f[p1][0] , f[p3][0] ....  f[pn][0] } +1

    最后枚举每一个点,每个点作为拐点,得到直径就是f[i][0]+f[i][1]

    取出最大值

    for( 1<= i <=n )

         max{  f[i][0]+f[i][1] }

      推荐题目:

    P3304 [SDOI2013]直径


    区间dp满足条件:

    1. 合并相邻的东西
    2. 把所有东西合并

     

             F[l][r]     第 l 堆式子到第 r 堆式子合并为一堆的最小代价

    边界条件:   f[i][i]=0

    最后一次合并,左右合并,两堆合成一堆

    整个合并过程中没有改变石头顺序

    所以两堆石头存在一个分界线,把它分成左右两堆

     显然要找到一个分界点(断点)p,然后分开左右两边

    f[l][p]  f[p+1][r]

    左右分开做,互不影响

    所以枚举一下分界线(断点)p

    最后结果就是:

    f [ l ] [ r ] = min ( f [ l ] [ p]  + f[ p + 1 ] [ r ] + sum [ l ] [ r ]  )

    f[l][p]      合并断点左边

    f[p+1][r] 合并断点右边

    sum[l][r] 合并这两堆(此处维护一个前缀和,更加方便)

    [代码]:

    由于求最小值,初始化最大 0x3f

    最好不用0x7f,两个0x7f加起来就爆int

     考虑如何循环

    枚举左端点,右端点,断点

    但这是错的

    在求f[l][r]是没有计算f[l][r],但却需要他来更新

    应该是小区间转移到大区间

     

    枚举区间长度,按长度,从小到大

    O(n3

    #include<iostream>
    
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    
    int main()
    {
        cin >>n;
        for (int a=1;a<=n;a++)
            cin >> z[a];
        memset(f,0x3f,sizeof(f));  //由于求最小值,初始化最大 0x3f 
    
        
        for (int a=1;a<=n;a++)
            f[a][a] = 0;  //边界条件
        
        for (int len=2;len<=n;len++)
            for (int l=1,r=len; r<=n; l++,r++)
                for (int p=l;p<r;p++)
                    f[l][r] = min(f[l][r],f[l][p]+f[p+1][r]+sum[l][r]);  //
            
        cout << f[1][n] << endl;
        
        return 0;
    }

    P1880 [NOI1995]石子合并

    上一题a1 an 不相邻,本题a1 an相邻,

    所以要补区间,扩成两倍,但还是只做了一遍DP

     f[1][n]是考虑a1  an 不相邻

     f[2][n+1]是考虑a1  an相邻,但是a2  a1 不相邻

    所以考虑一遍各种情况,每次都会有一个断开的位置,找出断开哪个位置使得结果最小

     推荐题目:

    P1063 能量项链


    给出点的坐标

    (x1,y1)(x2,y2)(x3,y3)……(xn,yn

     

    有一个人一开始在一号点

    出发,把其余的点都走至少一遍,使得路径最短

    这样也是可以的

    这叫TSP问题

    (旅行商问题)

    经典的NP-hard问题   最快也至少是O(2n),n在指数上

    (continue)

    结论:

    最优情况下,每个点只去一次

     

    DP每一步枚举所有可能的情况

    目前停留在5,它可以有三种情况的转移,因为还有三个点没走

    DP转移:

    希望把集合变成下标

    目前保持的状态是集合,要求压成数

    构造一个n位二进制数

    每个元素只有可能在或者不在集合里

    在 1

    不在 0

    所以说这个二进制数与集合{1,4,5}等价

    S,已经走过的点的集合

    F[ s ][ i ]从起点出发,走过集合s里的点,停留在i,所走过的最小距离

    初始化f[1][1]=0

    转移:考虑怎么走

    转移

    枚举j点,如果没走过它,我们把他加进去s试试

     相应的最短距离要更新

    转移方程

     

    把s的第j位从0变成1

    如果从i走到j比从起点到j近

    #include<cstdio>
    #include<iostream>
    
    using namespace std;
    
    const int maxn=20;
    
    int n;
    
    double f[1<<maxn][maxn];   //F[2^n][n]这么大就好
    
    double dis(int i,int j)
    {
        return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
    }
    
    int main()
    {
        cin >> n;
        for (int a=0;a<n;a++)   //a下标 0-n-1
            cin >> x[a] >> y[a];
            
        for (int a=0;a<(1<<n);a++)   //手动初始化数组
            for (int b=0;b<n;b++)
                f[a][b] = 1e+20;     //要求最小值,手动无穷大
                
        f[1][0] = 0;   //初始化边界 
        
        for (int s=1;s<(1<<n);s++)   //枚举S
            for (int i=0;i<n;i++)    //枚举最后停留在那个点,停在i点
                if ( ((s>>i) & 1) == 1)   // 看S二进制第i位是0还是1, 保证i在集合 s里
                 for (int j=0;j<n;j++)   // 枚举没走过的点 (j位一定为0)
                     if ( ((s>>j) & 1) == 0)  //s>>j,那么i位就到了第0位
                         f[s|(1<<j)][j] = min(f[s|(1<<j)][j] , f[s][i] + dis(i,j));//转移方程
                         
        double ans = 1e+20;
        for (int a=1;a<n;a++)   //不可能停在起点0号,所以从1枚举 
            ans = min (ans , f[(1<<n)-1][a]);  //2的N次方减一就是N个位都是1 ,也就是都走过了一遍点
            
        cout << ans <<endl;
            
    }

    它的时间复杂度是O(n^2 * s^n)
    空间复杂度O(2^n * n)
    (一秒钟的运算量在3*10^8~10^9之间)
    差不多n<=20

    补充一些复杂度有关的知识

    n<=12(O(n!)的复杂度,直接暴搜)
    n<=22 或20(状压O(n^2 * s^n))
    n<=32 (直接放弃)
    n<=50 (直接放弃)
    n<=100 (O(n^3))
    n<=1000 (O(n^2))
    n<=10^5 (数据结构 O(nlogn))
    n<=10^6 (线性算法 O(n))

    n>10^6(O(1))


    2019.7.17又讲一遍

    1. 平面上N个点,第i个点坐标(xi,yi),从1号点出发,在平面上任意走,求走遍所有点再回来的最短路径

    使得距离最短,一个点没必要走两次,每次从没走过的点选一个出来

    f[s][i]  i 在i点,s走过了哪些点

    状态压缩:把一个数组压缩成一个数

     

    自己更新别人

    所以我们要找没走过的点,枚举j,看看s的第j位二进制位是不是0

    最后答案看看停在那个点,最后还要回来

    #include<iostream>
    
    using namespace std;
    
    double f[][];
    double x[233],y[233];
    
    int main()
    {
        cin >> n;
        for (int a=0;a<n;a++)
            cin >> x[a] >> y[a];
        f=∞
        f[1][0]=0;
        for (int s=0;s<(1<<n);s++)
            for (int i=0;i<n;i++)
                if (f[s][i] < ∞)
                {
                    for (int j=0;j<n;j++)
                        if ( ((s>>j) & 1) == 0)
                        {
                            int news = s | (1<<j); //加上这个数字 
                            f[news][j] = min(f[news][j],f[s][i] + dis(i,j));
                        }
                }
        for (int i=0;i<n;i++)
            ans=min(ans, f[(1<<n)-1][i] + dis(i,0));
            
        return 0;
    }

    O(2^n  *  n^2)

    N<=22或者N<=20

    一定是先枚举s,然后s从小到大,因为走过的点是越来越多的

     推荐题目:

    P1171 售货员的难题


    题目每多一个条件就多一个维度(至少保留暴力分。。)

     

    Fij    从起点走到 i,j 的方案数

    每个坐标都可以向右或下走,除了边界,每个点由左边的点或上边的点转移过来

    边界只能由一个方向转移过来

    边界条件:f1j=fi1=1

    转移方程:fij=fi-1,j+fi,j-1

    其实也就是这样

    Cn-1n+m-2

     

    数字三角形

    希望走过的路径最大

    变化量:走的过程中,位置变化

    f[i][j] 走到(i,j)时走过路径的最大值

    可以从左上角或者正上方转移过来

    转移方程:f[ i ][ j ] = max( f[ i-1 ][ j ] , f[ i-1 ][ j-1 ] ) + a[ i ][ j ]

    Joyoi

    http://www.joyoi.cn/

     数字三角形2

    PS: 实在不行加一维

    f[i][j] 走到(i,j) %100 后的最大值

    错误

    前面的最优不一定保证后面最优

     

    比如你现在在98 99中选择,99一定最优,但是下一步再选,就会降为hin小的数

    维度不够加一维

    所以考虑加一维度

    bool  f[i][j][k]表示走到[i][j]时的路径和%100=k是否可行

    #include<iostream>
    
    using namespace std;
    
    bool f[233][233][233];  //bool数组 
    
    int main()
    {
        cin >> n;
        for (int i=1;i<=n;i++)
            for (int j=1;j<=i;j++)
                cin >> a[i][j];
                
        f[1][1][a[1][1] % 100] = true;  //初始化 
        for (int i=1;i<n;i++)
            for (int j=1;j<=i;j++)
                for (int k=0;k<100;k++)
                    if (f[i][j][k])  //判断之前是否可达,否则走就没必要 
                    {
                        f[i+1][j][(k+a[i+1][j])%100]=true;
                        f[i+1][j+1][(k+a[i+1][j+1])%100]=true;
                    }
        
        for (int j=1;j<=n;j++)   //最后枚举在哪一列 
            for (int k=0;k<100;k++)  
                if (f[n][j][k]) ans=max(ans,k);
        cout << ans << endl;
        
        return 0;
    }

    最长上升子序列

    (和导弹拦截有异曲同工之妙)

    数据加强到n<=105

    用数据结构 线段树 加速

     我的背包问题

    系统的学习详见背包九讲

     好趴下面放3个背包问题:

        

     01背包

    例题:采药

    N个物品,M个体积

    物品i,体积vi,价值wi

    头疼:设计状态

    放物品,考虑有什么未知量???

    第一维:放了多少物品,[i]放好了i个物品

    第二维:体积 [j] 已经放进去的物品,体积是多少

    F[i][j] 放进i个物品,体积为j的最大价值

    放进物品的编号<=i ,每个物品可以放或者不放,j已经放进去i个物品的体积之和

    如何转移???

    每次都考虑好了前i种物品,那么就考虑第i+1放或者不放

    如果不放第i +1个物品,体积不变,价值不变

    放了后体积会+vi,价值+wi

     f[ i ][ j ] = max( f[ i-1 ][ j ]  ,  f[ i-1 ][ j-v[i] ] + wi )

    外层for循环维度

      状态转移

    J要判断啊,不可以超过背包容积

    最后答案不一定是f[n][m],因为不一定体积越大价值越大,但是答案一定是考虑了所有n个物品,所以for一遍体积,ans取max

        for (int i=1;i<=n;i++)
            for (int j=0;j<=m;j++)
            {
                f[i][j] = f[i-1][j];
                if (j >= v[i]) f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
            }
            
        int ans=0;
        for (int a=0;a<=m;a++)
            ans = max(ans,f[n][a]);
        cout << ans << endl;

     

      无限背包问题

     

    从别人转移到自己

    放0个:f[i-1][j]

    放1个:f[i-1][j-vi]

    放2个:f[i-1][j-2*vi]

    放k个:f[i-1][j-k*vi]

    可以枚举第i个物品到底放了几个

    for (int i=1;i<=n;i++)
            for (int j=0;j<=m;j++)
                for (int k=0;k*v[i]<=j;k++)
                    f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);

    要判断k*v[i]<=j,保证体积不会爆炸

    But 3层循环 O(n^3),慢了hin多

    如何优化???

    其实不需要降维,只需要把01背包改一下

    f[ i ][ j ] = max( f[ i ][ j ] , f[ i ][ j-v[i] ] + w[ i ] )

     

    之前是由i-1这种i的上一层状态转移过来的

    此时就可以同一行的转移,横向转移,转移多少次就相当于一种物品放了多少个

    复杂度 : O(nm)

    for (int i=1;i<=n;i++)
            for (int j=0;j<=m;j++)
            {
                f[i][j] = f[i-1][j];
                if (j >= v[i]) f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);
            }
        int ans=0;
        for (int a=0;a<=m;a++)
            ans = max(ans,f[n][a]);
        cout << ans << endl;

      有限背包问题

     

    直接k枚举多少次 O(n^3)

    考虑优化???

    比如现在一个物品能放13次

    造体积为vi,价值为wi,只能用1次的物品

    造体积为2vi,价值为2wi,只能用1次的物品

    造体积为4vi,价值为4wi,只能用1次的物品

    造体积为6vi,价值为6wi,只能用1次的物品

    变成捆绑包的组合

    01背包问题

    O(n^2  *  k)

    如何处理捆绑包大小???

    相当于二进制拆分

    (1)如果一个数字可以被2^k这样111111分解,那么很显然捆绑包组合起来可以实现所有值

    (2)那如果不能111111这样分解,最后一定会剩下一个数字,把这个数字打包成一个捆绑包,也就是你算的时候,先算上这个数字,然后后面就变成一个可以被1111拆分的,就又回到上面情况

    拆出来捆绑包个数k≈logn

    所以复杂度就是O( nm logn )

    只需要读入处理一下

    #include<iostream>
    
    using namespace std;
    
    int n,m,w[233],v[233];
    int f[233][233];
    
    int main()
    {
        cin >> n >> m;
        int cnt = 0;
        for (int a=1;a<=n;a++)
        {
            int v_,w_,z;
            cin >> v_>> w_ >> z;
            
            int x = 1;
            while (x <= z)
            {
                cnt ++;
                v[cnt] = v_*x;
                w[cnt] = w_*x;
                z-=x;
                x*=2;
            }
            if (z>0)  //Z最后有剩余,新建一个捆绑包 
            {
                cnt ++;
                v[cnt] = v_*z;
                w[cnt] = w_*z;
            }
        }
        n=cnt;
        for (int i=1;i<=n;i++)
            for (int j=0;j<=m;j++)
            {
                f[i][j] = f[i-1][j];
                if (j >= v[i]) f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
            }
        int ans=0;
        for (int a=0;a<=m;a++)
            ans = max(ans,f[n][a]);
        cout << ans << endl;
        return 0;
    }

    最后是一个无处安放的DP

    P4408 [NOI2003]逃学的小孩

    以及网址

    http://west14.openjudge.cn/

  • 相关阅读:
    C# 将信息打印/输出到VS的“输出”窗口
    USB、UART、232串口通信和转换芯片
    UART/USB你必须知道的
    摄像头捕获视频流软件AMCAP使用教程(视频采集捕获处理媒体制作微型软件)
    Qt 线程关闭
    Qt 5种标准对话框及使用方法详解 QFileDialog、QcolorDialog、QFontDialog、QinputDialog、QMessageBox
    OpenCV 机器人手眼标定(九点标定法)
    如何打开svs文件
    OpenCV cvEstimateRigidTransform函数详细注解
    OpenCV 几何图像变换-cv::getAffineTransform(仿射变换)
  • 原文地址:https://www.cnblogs.com/xiaoyezi-wink/p/10795613.html
Copyright © 2011-2022 走看看