• DP总结


    短暂的一年OI学习将要告别,回想一年DP从无到有,想总结一番.

    DP:最优子结构+阶段状态转移.

    ①:区间DP

    其实感觉挺套路的,枚举区间长,L,R,即可.

    T1:石子合并

    sol:从这个题学习到了两点,一个就是拆环为链的技巧,二个就是我们发现要先枚举len,在枚举L,R,len从小到大,这就体现了DP无后效性阶段性的转移,然后对阶段取max即可.

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    #define e exit(0)
    #define re register
    #define LL long long
    const LL inf = 1e11;
    LL n,Max,Min,val[301],sum[301],f[301][301],g[301][301];
    inline LL fd(){
        LL s=1,t=0;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    int main()
    {
        n =  fd();
        for(re LL i=1;i<=n;++i){
            val[i] = fd();
            val[i+n] = val[i];
        }
        n = (n<<1);
        memset(g,0,sizeof(g));
        memset(f,0x7f,sizeof(f));
        for(re LL i=1;i<=n;++i)
            sum[i] = sum[i-1]+val[i];
        for(re LL i=1;i<=n;++i)
            f[i][i] = 0;
        for(re LL len=2;len<=n;++len){
            for(re LL L=1;L+len-1<=n;++L){
                LL R = L+len-1;
                for(re LL k=L;k<R;++k){
                    f[L][R] = min(f[L][R],f[L][k]+f[k+1][R]);
                    g[L][R] = max(g[L][R],g[L][k]+g[k+1][R]);
                }
                f[L][R] += sum[R]-sum[L-1];
                g[L][R] += sum[R]-sum[L-1];
            }
        }
        Min = 1e11,Max = -1;
        n = (n>>1);
        for(re LL i=1;i<=n;++i){
            Min = min(Min,f[i][i+n-1]);
            Max = max(Max,g[i][i+n-1]);
        }
        printf("%lld
    %lld",Min,Max);
        return 0;
    }
    View Code

    T2:Polygon

    sol:照样拆环为链,按照区间DP,不过要多记录一维,f[L][R][0/1]表示区间最小/最大值,因为最大值可能由两个最小值(负数)相乘得来,最小值可能是最大值乘最小值(正 x 负).

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    using namespace std;
    #define e exit(0)
    #define re register
    #define LL long long
    #define inf 1e11
    #define Max(x,y)x>y?x:y
    #define Min(x,y)x<y?x:y
    const LL maxn = 51;
    char fh[maxn],bri[maxn][maxn];
    LL n,l1,l2,num[maxn],f[maxn][maxn][2];
    inline LL fd(){
        LL s=1,t=0;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    int main()
    {
        freopen("POJ1179.in","r",stdin);
        freopen("POJ1179.out","w",stdout);
        n = fd();
        for(re LL i=1;i<=(n<<1);++i){
            if(i&1) scanf("%c",&fh[++l1]);
            else{
                num[++l2] = fd();
                num[l2+n] = num[l2];
            }
        }
        int id = 2;
        for(re LL i=1;i<=n;++i){
            bri[i][i+1] = fh[id++];
            if(id>n) id = 1;
            bri[i+n][i+1+n] = bri[i][i+1];
        }
        n = (n<<1);
        for(re LL i=1;i<=n;++i)
            for(re LL j=1;j<=n;++j)
                f[i][j][0] = inf,f[i][j][1] = -inf;
        for(re LL i=1;i<=n;++i)
            f[i][i][0] = f[i][i][1] = num[i];
        for(re LL len=2;len<=n;++len)
            for(re LL L=1;L+len-1<=n;++L){
                int R = L+len-1;
                for(re LL k=L;k<R;++k){
                    if(bri[k][k+1] == 't'){
                        f[L][R][0] = min(f[L][R][0],f[L][k][0]+f[k+1][R][0]);
                        f[L][R][1] = max(f[L][R][1],f[L][k][1]+f[k+1][R][1]);
                    }
                    else if(bri[k][k+1] == 'x'){
                        f[L][R][0] = min(f[L][R][0],f[L][k][1]*f[k+1][R][0]);
                        f[L][R][0] = min(f[L][R][0],f[L][k][0]*f[k+1][R][1]);
                        f[L][R][0] = min(f[L][R][0],f[L][k][0]*f[k+1][R][0]);
                        f[L][R][0] = min(f[L][R][0],f[L][k][1]*f[k+1][R][1]);
                        f[L][R][1] = max(f[L][R][1],f[L][k][1]*f[k+1][R][1]);
                        f[L][R][1] = max(f[L][R][1],f[L][k][0]*f[k+1][R][0]);
                    }
                }
            }
        n = (n>>1);
        LL ans = 0;
        for(re LL i=1;i<=n;++i)
            ans = max(ans,f[i][i+n-1][1]);
        printf("%lld
    ",ans);
        for(re LL i=1;i<=n;++i)
            if(f[i][i+n-1][1] == ans)
                printf("%lld ",i);
        return 0;
    }
    View Code

    T3:能量项链

    sol:照样拆环为链,但这个题比较诡异的地方就是注意拆环为链后头尾坐标,转移的时候注意head[L]*tail[k]*tail[R].

    #include<iostream>
    #include<cstdio>
    using namespace std;
    #define R register
    int n,ans=-1,head[310],tail[310],f[310][310];
    int main()
    {    
    //    freopen("s.in","r",stdin);
    //    freopen("s.out","w",stdout);
        scanf("%d",&n);
        for(R int i=1;i<=n;++i)
        {
            scanf("%d",&head[i]);
            head[i+n]=head[i];
        }
        for(R int i=1;i<=2*n-1;++i)
            tail[i]=head[i+1];
        tail[2*n]=head[1];
        for(R int i=1;i<=2*n-1;++i)
            f[i][i]=0;
        for(R int len=1;len<=n-1;++len){
            for(R int i=1;i<=2*n-len;++i){
                int j=i+len;
                for(R int k=i;k<=j-1;++k)
                    f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+head[i]*tail[k]*tail[j]);
            }
        }
        for(R int i=1;i<=n;++i)
            ans=max(ans,f[i][i+n-1]);
        printf("%d",ans);
        return 0;
    }
    View Code

    T4:凸多边形的三角划分

    给定一具有 N 个顶点从 1 到 N 编号的凸多边形,每个顶点的权均已知.问如何把 这个凸多边形划分成 N-2 个互不相交的三角形,使得这些三角形顶点的权的乘积之和最小?

    忽略这张大图

    sol:还是区间DP套路,f[L][R]表示顺序划分[L,R]子多边形最小的权值,转移时注意f[L][R] = min{f[L][k]+f[k][R]+val[L]*val[R]*val[k]}.显然这个题的难点是我们要将点'强行'定编号确定后效形,还要注意细节:k∈[L+1,R-1].

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    #define e exit(0)
    #define re register
    #define LL long long
    long long n,val[110],f[110][110];
    long long getv(LL x,LL y,LL z){return val[x]*val[y]*val[z];}
    int main()
    {
        freopen("division.in","r",stdin);
        freopen("division.out","w",stdout);
        scanf("%lld",&n);
        for(re LL i=1;i<=n;++i)
            scanf("%lld",&val[i]);
        memset(f,0x7f,sizeof(f));
        for(re LL len=2;len<=n;++len)
            for(re LL i=1;i+len-1<=n;++i){
                LL j=i+len-1;
                if(j==i+1) f[i][j]=0;
                for(re LL k=i+1;k<=j-1;++k)
                    f[i][j]=min(f[i][j],f[i][k]+f[k][j]+getv(i,k,j));
            }
        printf("%lld",f[1][n]);
        return 0;
    }
    View Code

    T5:看这里的T4,T3.

     ②状压DP:

    做过的题都觉的挺套路的,难点都在如何用二进制运算去满足条件.

    T1:广场铺砖

    sol:其实这个题算状压DP'状态设计'中比较有意思的题.设横放的为'0',竖放的的为'1',这样做是为了上下转移判断合法,s3 = s1|s2,s3只能有偶数个'1'.且s1,s2上下不能同为1.

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    #define e exit(0)
    #define R register
    #define ll long long
    int main()
    long long W,H,num,sum,f[13][2050],check[2050];
    {
        freopen("floor.in","r",stdin);
        freopen("floor.out","w",stdout);
        scanf("%lld%lld",&W,&H);
        if((W&1)&&(H&1)){
            printf("0");
            return 0;
        }
        for(R int i=0;i<(1<<H);++i){
            num=sum=0;
            for(R int j=0;j<H;++j)
                if((1<<j)&i)
                    num|=sum,sum=0;
                else sum^=1;
            check[i]=num|sum ? 0: 1;
        }
        f[0][0]=1;
        for(R ll i=1;i<=W;++i)
            for(R ll j=0;j<(1<<H);++j)
            {
                f[i][j]=0;
                for(R ll k=0;k<(1<<H);++k)
                    if(!(j&k)&&check[j|k])
                        f[i][j]+=f[i-1][k];
            }
        printf("%lld",f[W][0]);
        return 0;
    }
    View Code

    T2:玉米地

    sol:f[x][sta]第x行且状态为sta的总方案.涉及到状压DP计数的一个模板.只要上下&并不冲突,并不包含'贫瘠土地'即可.

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<vector>
    using namespace std;
    #define e exit(0)
    #define re register
    #define LL long long
    const LL maxn = (1<<13);
    const LL mod = 100000000;
    vector<LL> q[20];
    LL m,n,Maxm,Maxn,ans,ma[20][20],f[20][maxn];
    inline LL fd(){
        LL s=1,t=0;
        char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    bool checkone(LL x,LL statue){
        bool flag = (statue & statue<<1)>0;
        if(flag) return false;
        LL len = q[x].size();
        for(re LL i=0;i<len;++i){
            LL y = q[x][i];
            if((1<<y)&statue) return false;
        }
        return true;
    }
    bool checktwo(LL s1,LL s2){
        if(s1&s2) return false;
        return true;
    }
    int main()
    {
        m = fd(),n = fd();
        Maxm = (1<<m),Maxn = (1<<n);
        for(re LL i=0;i<m;++i)
            for(re LL j=0;j<n;++j){
                ma[i][j] = fd();
                if(ma[i][j] == 0)
                    q[i].push_back(j);
            }
        f[0][0] = 1;
        for(re LL statue=1;statue<Maxn;++statue){
            if(!checkone(0,statue))
                continue;
            f[0][statue] = 1;
        }
        for(re LL i=1;i<m;++i){
            for(re LL s1=0;s1<Maxn;++s1){
                if(!checkone(i,s1)) continue;
                for(re LL s2=0;s2<Maxn;++s2){
                    if(!checktwo(s1,s2)) continue;
                    f[i][s1] = (f[i][s1]+f[i-1][s2])%mod;
                }
            }
        }
        for(re LL statue=0;statue<Maxn;++statue)
            ans = (ans+f[m-1][statue])%mod;
        printf("%lld",ans);
        return 0;
    }
    View Code

    T3:互不侵犯

    sol:f[x][sta][k]第x行且状态为sta,总共放了k个国王的方案.其实就'玉米地'多去判断&后的相邻位置是否都为'1'.

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    #define e exit(0)
    #define re register
    #define LL long long
    const LL maxn = (1<<10);
    LL n,k,Maxn,ans,num[maxn],f[10][100][maxn];
    inline LL fd(){
        LL s=1,t=0;
        char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    LL work(LL x){
        LL cnt = 0;
        while(x){
            ++cnt;
            x = x&(x-1);
        }
        return cnt;
    }
    bool checkone(LL x){return (x&x<<1)>0;}
    bool checktwo(LL s1,LL s2){
        if(s1&s2) return false;
        LL s3 = s1|s2;
        if(checkone(s3)) return false;
        return true;
    }
    int main()
    {
        n = fd(),k = fd();
        Maxn = (1<<n);
        for(re LL statue = 0;statue<Maxn;++statue)
            num[statue] = work(statue);
        f[0][0][0] = 1;
        for(re LL statue = 1;statue<Maxn;++statue){
            if(checkone(statue)) continue;
            f[0][num[statue]][statue] = 1;
        }
        for(re LL i=1;i<n;++i)
            for(re LL s1=0;s1<Maxn;++s1){
                if(checkone(s1)) continue;
                for(re LL s2=0;s2<Maxn;++s2){
                    if(checkone(s2)) continue;
                    if(!checktwo(s1,s2)) continue;
                    LL s = num[s1];
                    for(re LL sum=0;sum<=i*n;++sum){
                        if(!f[i-1][sum][s2]) continue;
                        LL nows = sum+s;
                        f[i][nows][s1] += f[i-1][sum][s2]; 
                    }
                }
            }
        for(re LL s = 0;s<Maxn;++s)
            ans += f[n-1][k][s];
        printf("%lld",ans);
        return 0;
    }
    View Code

    T4:炮兵阵地

    sol:可以设计f[x][s1][s2],表示第x行,此行状态为s1,x-1行状态为s2,可以合法放的最多炮兵个数.其实可以提前处理好对于第x行合法的x-1行,x-2行,循环的次数会减少,'s2'也可以认为第几个合法的状态,这样时间空间都省下来了,对于每一行,也要判断左右相邻两格都不为1.

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #include<vector>
    using namespace std;
    #define e exit(0)
    #define re register
    #define LL int
    const LL maxn = (1<<10);
    vector<LL> D_one[101];
    vector<LL> D_two[maxn];
    char c[101][10];
    LL n,m,Maxm,ans,vis[maxn],num[maxn],f[100][550][550];
    inline LL fd(){
        LL s=1,t=0;
        char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    bool checkone(LL x){
        LL q[24],cnt = 0,id = 0;
        bool flag = (x&x<<1)>0;
        if(flag) return false;
        while(x){
            ++id;
            LL num = x%2;
            if(num == 1)
                q[++cnt] = id;
            x/=2;
        }
        sort(q+1,q+1+cnt);
        for(re LL i=2;i<=cnt;++i)
            if(q[i] - q[i-1]<=2)
                return false;
        return true;
    }
    bool checktwo(LL x,LL s){
        if(!checkone(s)) return false;
        LL len = D_one[x].size();
        for(re LL i=0;i<len;++i){
            LL t = D_one[x][i];
            if((1<<t)&s) return false;
        }
        return true;
    }
    bool checkIII(LL s1,LL s2){
        if(s1&s2) return false;
        return true;
    }
    LL work(LL x){
        LL cnt = 0;
        while(x){
            ++cnt;
            x = x&(x-1);
        }
        return cnt;
    }
    int main()
    {
        n = fd(),m = fd();
        for(re LL i=0;i<n;++i)
            for(re LL j=0;j<m;++j){
                cin>>c[i][j];
                if(c[i][j] == 'H')
                    D_one[i].push_back(m-j-1);
            }
        Maxm = (1<<m);
        for(re LL s = 0;s<Maxm;++s)
            num[s] = work(s);
        for(re LL i=0;i<n;++i)
            for(re LL s=0;s<Maxm;++s){
                if(!checktwo(i,s)) continue;
                D_two[i].push_back(s);
            }
        LL len = D_two[0].size();
        if(n == 1){
            for(re LL i=0;i<len;++i){
                LL s = D_two[0].size();
                ans = max(ans,num[s]);
            }
            printf("%d",ans);
            return 0;
        }
        for(re LL i=0;i<D_two[1].size();++i){
            LL s1 = D_two[1][i];
            for(re LL j=0;j<D_two[0].size();++j){
                LL s2 = D_two[0][j];
                if(!checkIII(s1,s2)) continue;
                f[1][i][j] = num[s1]+num[s2];
            }
        }
        for(re LL i=2;i<n;++i){
            LL len1 = D_two[i].size();
            for(re LL a=0;a<len1;++a){
                LL s1 = D_two[i][a];
                LL len2 = D_two[i-1].size();
                for(re LL b=0;b<len2;++b){
                    LL s2 = D_two[i-1][b];
                    if(!checkIII(s1,s2)) continue;
                    LL len3 = D_two[i-2].size();
                    for(re LL c=0;c<len3;++c){
                        LL s3 = D_two[i-2][c];
                        if(!checkIII(s2,s3)||!checkIII(s1,s3))
                            continue;
                        f[i][a][b] = max(f[i][a][b],f[i-1][b][c]+num[s1]);
                    }
                }
            }
        }
        LL len1 = D_two[n-1].size(),len2 = D_two[n-2].size();
        for(re LL i=0;i<len1;++i)
            for(re LL j=0;j<len2;++j)
                ans = max(ans,f[n-1][i][j]);
        printf("%d",ans);
        return 0;
    }
    View Code

    T5:愤怒的小鸟

    sol:f[x]表示状态为x的用的最少的小鸟.我们任选两个合法的小鸟作为一条抛物线,找出状态s'表示这个抛物线能打到的猪.然后对每一个状态暴力用每一条抛数线更新即可.

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    using namespace std;
    #define e exit(0)
    #define re register
    #define LD double
    const int maxn = (1<<19);
    int T,n,m,Maxn,f[maxn],a[20][20];
    struct DL{LD x,y;}Q[20];
    LD Fabs(LD a){return a>0?a:-a;}
    int main()
    {
        scanf("%d",&T);
        while(T--){
            memset(a,0,sizeof(a));
            memset(f,0x3f,sizeof(f));
            scanf("%d%d",&n,&m);
            Maxn = (1<<n);
            for(re int i=0;i<n;++i)
                scanf("%lf%lf",&Q[i].x,&Q[i].y);
            for(re int i=0;i<n;++i)
                a[i][i] |= (1<<i);
            for(re int i=0;i<n;++i)
                for(re int j=i+1;j<n;++j){
                    if(fabs(Q[i].x-Q[j].x)>1e-6){
                        LD x1 = Q[i].x,y1 = Q[i].y,x2 = Q[j].x,y2 = Q[j].y;
                        LD A = (x2*y1-x1*y2)/((x1-x2)*x1*x2);
                        LD B = (y1-A*x1*x1)/x1;
                        if(A > -1e-6) continue;
                        for(re int k=0;k<n;++k){
                            LD x = Q[k].x,y = Q[k].y;
                            if(fabs(A*x*x+B*x-y)<=1e-6)
                                a[i][j] |= (1<<k);
                        }        
                    }
                }
            f[0] = 0;
            for(re int i=0;i<Maxn;++i){
                for(re int j=0;j<n;++j)
                    for(re int k=j;k<n;++k)
                        f[i|a[j][k]] = min(f[i|a[j][k]],f[i]+1);
            }
            printf("%d
    ",f[Maxn-1]);
        }
        return 0;
    }
    View Code

    T6:骑士

    国际象棋中骑士的移动规则和中国象棋中的马是类似的,它先沿着一个方向移动两格,再沿着与刚才移动方向垂直的方向移动一格.路径上的棋子并不会影响骑士的移动,但是如
    果一个骑士走到了一个放有棋子的格子,它就会攻击那个棋子.现在有一个 n*n 的棋盘,有 k 个骑士需要被摆到棋盘上去.那么使所有骑士互不攻击的摆放方式一共有多少种呢?

    首先你要弄清骑士的攻击范围

    然后这个题就完了.

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<vector>
    using namespace std;
    #define e exit(0)
    #define re register
    const int maxn = (1<<8);
    int n,k,Maxn;
    double f[8][maxn][maxn][65];
    vector<int> Q[maxn];vector<int> G[maxn];
    inline int fd(){
        int s=1,t=0;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    bool checkone(int s1,int s2){
        int s4 = (s1<<2),s5 = (s1>>2);
        if((s2&s4)||(s2&s5)) return false;
        return true;
    }
    bool checktwo(int s1,int s2){
        int s4 = (s1<<1),s5 = (s1>>1);
        if((s2&s4)||(s2&s5)) return false;
        return true;
    }
    int getsum(int x){
        int cnt = 0;
        while(x){++cnt;x = x&(x-1);}
        return cnt;
    }
    void propre(){
        memset(f,0,sizeof(f));
        for(re int i=0;i<Maxn;++i){
            int t = getsum(i);
            f[0][i][0][t] = 1;
        }
        for(re int s1=0;s1<Maxn;++s1)
            for(re int s2=0;s2<Maxn;++s2){
                if(!checkone(s1,s2)) continue;
                int t1 = getsum(s1),t2 = getsum(s2);
                f[1][s1][s2][t1+t2] += f[0][s2][0][t2];
            }
    }
    void slove(){
        for(re int i=2;i<n;++i){
            for(re int sta=0;sta<Maxn;++sta){
                int len1 = Q[sta].size(),len2 = G[sta].size(),p = getsum(sta);
                for(re int x=0;x<len1;++x){
                    int s1 = Q[sta][x];
                    for(re int y=0;y<len2;++y){
                        int s2 = G[sta][y];
                        if(!checkone(s1,s2)) continue;
                        for(re int t=0;t<=k;++t){
                            int s = t+p;
                            f[i][sta][s1][s] += f[i-1][s1][s2][t];
                        }
                    }
                }
            }
        }
        double ans = 0;
        for(re int s1=0;s1<Maxn;++s1)
            for(re int s2=0;s2<Maxn;++s2){
                ans += f[n-1][s1][s2][k];
            }
        printf("%.0lf",ans);
    }
    int main()
    {
        freopen("knight.in","r",stdin);
        freopen("knight.out","w",stdout);
        n = fd(),k = fd();
        Maxn = (1<<n);
        for(re int i=0;i<Maxn;++i)
            for(re int j=0;j<Maxn;++j){
                if(!checkone(i,j))continue;
                Q[i].push_back(j);
            }
        for(re int i=0;i<Maxn;++i)
            for(re int j=0;j<Maxn;++j){
                if(!checktwo(i,j)) continue;
                G[i].push_back(j);
            }
        propre();slove();
        return 0;
    }
    View Code

     树形DP:

    可以想象树形父子关系的每一层就是一个DP转移阶段,天然后效性.

    T1:没有上司的舞会

    sol:f[x][0/1]表示第x个节点选不选,保证x即x的子树都合法的前提下,能得到的最大值快乐值.可知当x选时,x的儿子必不选,x不选时,x的儿子可选可不选.

    #include<iostream>
    #include<cstdlib>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    #define e exit(0)
    #define re register
    const int maxn = 6010;
    int n,cnt,rt,U[maxn],head[maxn],val[maxn],f[maxn][2];
    struct bian{int to,next;}len[maxn];
    inline int fd(){
        int s=1,t=0;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    void add(int from,int to){
        len[++cnt].to = to;
        len[cnt].next = head[from];
        head[from] = cnt;
    }
    void dp(int x){
        for(re int k=head[x];k;k=len[k].next){
            int to = len[k].to;
            dp(to);
            f[x][1] += f[to][0];
            f[x][0] += max(f[to][1],f[to][0]);
        }
        f[x][1] += val[x];
    }
    int main()
    {
        n = fd();
        for(re int i=1;i<=n;++i)
            val[i] = fd();
        for(re int i=1;i<=n-1;++i){
            int x = fd(),y = fd();
            add(y,x);
            ++U[x];
        }
        int x = fd(),y = fd();
        for(re int i=1;i<=n;++i)
            if(!U[i]) rt = i;
        dp(rt);
        printf("%d",max(f[rt][0],f[rt][1]));
        return 0;
    }
    View Code

    T2:选课

    sol:很经典的树上泛化背包问题,对此可以应用于多叉树,并不只限于二叉树,在code中有重要解释.

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    using namespace std;
    #define e exit(0)
    #define re register
    const int maxn = 401;
    int n,m,cnt,val[maxn],head[maxn],f[maxn][maxn];
    struct bian{int to,next;}len[maxn];
    inline int fd(){
        int s=1,t=0;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    void add(int from,int to){
        len[++cnt].to = to;
        len[cnt].next = head[from];
        head[from] = cnt;
    }
    void dp(int x){
        f[x][0] = 0;
        for(re int k=head[x];k;k=len[k].next){
            int to = len[k].to;
            dp(to);
                    //倒序,类似于01背包的先后更新顺序.
            for(re int t=m;t>=0;--t)
                for(re int j=0;j<=t;++j){//倒序顺序即可.
                            
                    if(t-j>=0)    f[x][t] = max(f[x][t],f[x][t-j]+f[to][j]);
                }
        }
        if(x != 0)
            for(re int t=m;t>0;--t)
                f[x][t] = f[x][t-1]+val[x];//不能取max,f[x][t]未更新前是儿子选t的状态,并不是它自己选t的状态
    }
    int main()
    {
        n = fd(),m = fd();
        for(re int i=1;i<=n;++i){
            int x = fd();val[i] = fd();
            int y = i;
            add(x,y);
        }
        dp(0);
        printf("%d",f[0][m]);
        return 0;
    }                                                        
    View Code

     T3:通往自由的钥匙

    通向自由的钥匙被放 n 个房间里,这 n 个房间由 n-1 条走廊连接.但是每个房间里都有 特别的保护魔法,在它的作用下,我无法通过这个房间,也无法取得其中的钥匙.虽然我可
    以通过消耗能量来破坏房间里的魔法,但是我的能量是有限的.那么,如果我最先站在 1 号房间(1 号房间的保护魔法依然是有效的,也就是,如果不耗费能量,我无法通过 1 号房间,也无法取得房间中的钥匙),如果我拥有的能量为 P,我最多能取得多少钥匙?

    sol:开始感觉与上面一题相似,其实不然,由于这个背包带权,很多细节不一样,在code中重要解释.

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    #define e exit(0)
    #define re register
    const int N = 301;
    int n,p,cnt,cost[N],v[N],head[N],f[N][N];
    struct bian{int to,next;}len[N];
    inline int fd(){
        int s=1,t=0;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    void add(int from,int to){
        len[++cnt].to = to;
        len[cnt].next = head[from];
        head[from] = cnt;
    }
    void Dfs(int x,int fa){
        for(re int j=p;j>=cost[x];--j)
            f[x][j] = max(f[x][j],f[x][j-cost[x]]+v[x]);
        //提前将v[x]的影响加上,先强制选x.
        for(re int k=head[x];k;k=len[k].next){
            int to = len[k].to;
            if(to == fa) continue;
            Dfs(to,x);
            //为什么一定要大于等于cost[x],而不是像上一个题大于等于0?由于这个背包带权,我们不能让不合法的状态(<cost[x])有值.
            //并且有强制选x的意思.
            for(re int m=p;m>=cost[x];--m)
                for(re int t=m;t>=cost[x];--t)
                    f[x][m] = max(f[x][m],f[x][t]+f[to][m-t]);
        }
    }
    int main()
    {
        freopen("key.in","r",stdin);
        freopen("key.out","w",stdout);
        n = fd(),p = fd();
        for(re int i=1;i<=n;++i)
            cost[i] = fd(),v[i] = fd();
        for(re int i=1;i<=n-1;++i){
            int x = fd(),y = fd();
            add(x,y),add(y,x);
        }    
        Dfs(1,0);
        printf("%d",f[1][p]);
        return 0;
    }
    View Code

    T4:警卫安排

    一个重要的基地被分为 n 个连通的区域.出于某种神秘的原因,这些区域以一个区域为核心,呈一颗树形分布.在每个区域安排警卫所需要的费用是不同的,而每个区域的警卫都可以望见其相邻的区域,只要一个区域被一个警卫望见或者是安排有警卫,这个区域就是安全的.你的任务是在确保所有区域都是安全的情况下,找到安排警卫的最小费用.

    sol:T1的升级版.f[x][0/1/2]表示它被父亲,自己,儿子选,保证它即它的子树都合法的前提下,安排警卫的最小费用,f[x][0]可以转移f[son][1/2],f[x][1]可以转移f[son][0/1/2],f[x][2]则要强制选一个儿子,其他选最优情况.

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    #define e exit(0)
    #define re register
    const int maxn=730;
    int n,m,from,to,root,cnt,val[maxn],head[maxn],du[maxn],cu[maxn],f[maxn][3];
    struct bian{
        int to,next;
    }len[maxn<<1];
    inline int fd(){
        int s=1,t=0;
        char c=getchar();
        while(c<'0'||c>'9'){
            if(c=='-')
                s=-1;
            c=getchar();
        }
        while(c>='0'&&c<='9'){
            t=t*10+c-'0';
            c=getchar();
        }
        return s*t;
    }
    void add(int from,int to){
        len[++cnt].to=to;
        len[cnt].next=head[from];
        head[from]=cnt;
    }
    void dp(int x){
        f[x][1] = val[x];
        f[x][2] = 0;
        for(re int k=head[x];k;k=len[k].next){
            int to=len[k].to;
            dp(to);
            f[x][2] += min(f[to][1],f[to][0]);
            f[x][1] += min(f[to][0],min(f[to][1],f[to][2]));
        }
        int ans = 2139062143;
        for(re int k=head[x];k;k=len[k].next){
            int to=len[k].to;
            int v=f[to][1];
            for(re int g=head[x];g;g=len[g].next){
                int t=len[g].to;
                if(t==to) continue;
                v += min(f[t][0],f[t][1]);
            }
            ans = min(ans,v);
        }
        f[x][0] = ans;
    }
    int main()
    {
        freopen("security.in","r",stdin);
        freopen("security.out","w",stdout);
        n=fd();
        for(re int i=1;i<=n;++i){
            from=fd(),val[from]=fd(),m=fd();
            for(re int i=1;i<=m;++i){
                to=fd(),add(from,to);
                ++du[to],++cu[from];
            }
        }
        for(re int i=1;i<=n;++i)
            if(!du[i]){
                root=i;
                break;
            }
        memset(f,0x7f,sizeof(f));
        dp(root);
        printf("%d",min(f[root][0],f[root][1]));
        return 0;
    }
    View Code

    T5:消防局的设立

    sol:T4的升级版,多分情况讨论即可.设f[x][0/1/2/3/4].f[x][0]保证x的爷爷,x的父亲,x,即x的子树都安全的最少消防局数量;f[x][1]保证x的父亲,x,即x的子树都安全的最少消防局数量;f[x][0]保证x即x的子树都安全的消防局数量...要注意这里的f[x][0/1/2/3/4]与T4的定义是不同的.接下来的转移就贪心地分类讨论即可,借鉴一下LG题解.

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    #define e exit(0)
    #define re register
    #define inf 19260817
    const int maxn = 1010;
    int n,cnt,head[maxn],f[maxn][5];
    struct bian{int to,next;}len[maxn];
    inline int fd(){
        int s=1,t=0;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    void add(int from,int to){
        len[++cnt].to = to;
        len[cnt].next = head[from];
        head[from] = cnt;
    }
    void DP(int x){
        f[x][0] = 1,f[x][3] = f[x][4] = 0;
        for(re int k=head[x];k;k=len[k].next){
            int to = len[k].to;
            DP(to);
            f[x][0] += f[to][4];
            f[x][3] += f[to][2];
            f[x][4] += f[to][3];
        }
        if(head[x] == 0)
            f[x][0] = f[x][1] = f[x][2] = 1;
        else{
            f[x][1] = f[x][2] = inf;
            for(re int k=head[x];k;k=len[k].next){
                int s = len[k].to;
                int v1 = f[s][0],v2 = f[s][1];
                for(re int g=head[x];g;g=len[g].next){
                    int t = len[g].to;
                    if(s == t) continue;
                    v1 += min(f[t][2],f[t][3]),v2 += f[t][2];
                }
                f[x][1] = min(f[x][1],v1);
                f[x][2] = min(f[x][2],v2);
            }
        }
        for(re int i=1;i<=4;++i)
            f[x][i] = min(f[x][i],f[x][i-1]);
    }
    int main()
    {
        n = fd();
        for(re int i=2;i<=n;++i){
            int x = fd(),y = i;
            add(x,y);
        }
        DP(1);
        printf("%d",f[1][2]);
        return 0;
    }
    View Code

    T6:这里的T4.

     背包:

    看似最基础的,但个人认为是最重要的.

    T1:01背包.

    sol:主要注意倒序转移,用之前的状态更新当前的状态,即代表物品在这个阶段只用一次.

    #include<iostream>
    #include<cstdio>
    using namespace std;
    #define R register
    const int maxn=1010;
    int v,m,ti[maxn],val[maxn],f[maxn];
    inline int fd()
    {
        int s=1,t=0;
        char c=getchar();
        while(c<'0'||c>'9')
        {
            if(c=='-')
                s=-1;
            c=getchar();
        }
        while(c>='0'&&c<='9')
        {
            t=t*10+c-'0';
            c=getchar();
        }
        return s*t;
    }
    int main()
    {
        //freopen("s.in","r",stdin);
        //freopen("s.out","w",stdout);
        v=fd(),m=fd();
        for(R int i=1;i<=m;++i)
            ti[i]=fd(),val[i]=fd();
        for(R int i=1;i<=m;++i)
            for(R int j=v;j>=ti[i];--j)
                f[j]=max(f[j],f[j-ti[i]]+val[i]);
        printf("%d",f[v]);
        return 0;
    }
    View Code

    T2:完全背包.

    sol:注意正序转移,用现在的状态更新现在状态,即代表物品在这个阶段使用多次.

    #include<iostream>
    #include<cstdio>
    using namespace std;
    #define R register
    const int maxn=1e5+10;
    int v,m,f[maxn],ti[maxn],val[maxn];
    inline int fd()
    {
        int s=1,t=0;
        char c=getchar();
        while(c<'0'||c>'9')
        {
            if(c=='-')
                s=-1;
            c=getchar();
        }
        while(c>='0'&&c<='9')
        {
            t=t*10+c-'0';
            c=getchar();
        }
        return s*t;
    }
    int main()
    {
    //    freopen("s.in","r",stdin);
    //    freopen("s.out","w",stdout);
        v=fd(),m=fd();
        for(R int i=1;i<=m;++i)
            ti[i]=fd(),val[i]=fd();
        for(R int i=1;i<=m;++i)
            for(R int j=ti[i];j<=v;++j)
                f[j]=max(f[j],f[j-ti[i]]+val[i]);
        printf("%d",f[v]);
        return 0;
    }
    View Code

    T3:01背包变形

    en,没有题面,就是将01背包的容积扩大到1e9范围,但Σ价值的范围控制在1e6内.

    sol:改一下f[i]定义即可,f[i]表示能达到i价值时使用的背包最小值.

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    #define e exit(0)
    #define re register
    #define inf 19260817
    const int maxn = 3e5;
    int t,m,sumc,v[maxn],c[maxn],f[maxn];
    inline int fd(){
        int s=1,t=0;
        char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    int main()
    {
        t = fd(),m = fd();
        for(re int i=1;i<=m;++i){
            v[i] = fd(),c[i] = fd();
            sumc += c[i];
        }
        for(re int i=1;i<=sumc;++i)
            f[i] = inf;
        f[0] = 0;
        for(re int i=1;i<=m;++i)
            for(re int j=sumc;j>=c[i];--j){
                f[j] = min(f[j],f[j-c[i]]+v[i]);
            }
        for(re int i=sumc;i>=0;--i){
            if(f[i]<=t){
                printf("%d",i);
                break;
            }
        }
        return 0;
    }
    View Code

    T4:完全背包变形

    sol:选择最小物品,利用迪杰斯特拉,去构造关于它的剩余系即可(我可能在滥用'剩余系').

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #include<queue>
    using namespace std;
    #define e exit(0)
    #define re register
    #define inf 19260817
    const int maxn = 6e4;
    int n,mod,q,a[maxn+10],dis[maxn+10];
    inline int fd(){
        int s=1,t=0;
        char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    void dj(){
        for(re int i=0;i<maxn;++i)
            dis[i] = inf;
        dis[0] = 0;
        priority_queue<pair<int,int> >q;
        q.push(make_pair(0,0));
        while(q.size()){
            int v = q.top().first;
            int now = q.top().second;
            q.pop();
            v = -v;
            if(dis[now]!=v) continue;
            for(re int i=2;i<=n;++i){
                int id = (now+a[i])%mod;
                if(dis[id] > dis[now]+a[i]){
                    dis[id] = dis[now]+a[i];
                    q.push(make_pair(-dis[id],id));
                }
            }
        }
    }
    int main()
    {
        n = fd();
        for(re int i=1;i<=n;++i)
            a[i] = fd();
        sort(a+1,a+1+n);
        mod = a[1];
        dj();
        q = fd();
        for(re int i=1;i<=q;++i){
            int num = fd();
            if(dis[num%mod]>num) printf("No
    ");
            else printf("Yes
    ");
        }    
        return 0;
    }
    View Code

     T5:多重背包

    给定N种物品,其中第i种物品的体积为vi,价值为wi,并且有ci个.有一个容积为M的背包,选择若干个物品选入,体积不超过M,求最大价值.

    sol:可以暴力拆ci为1,在做01背包,也可以将ci 二进制拆分,利用唯一分解定理,也可做.

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    #define e exit(0)
    #define re register
    const int M = 10001;
    int n,lx,cost[M],num[M],val[M];
    struct DL{int v,c;}Q[M<<2];
    inline int fd(){
        int s=1,t=0;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    int main()
    {
        freopen("kkk.in","r",stdin);
        freopen("kkk.out","w",stdout);
        n = fd();
        for(re int i=1;i<=n;++i)
            cost[i] = fd(),val[i] = fd(),num[i] = fd();
        for(re int i=1;i<=n;++i){
            for(re int j=1;j<=num[i];j<<1){
                Q[++lx].c = val[i]*j;
                Q[lx].v = j*cost[i];
                num[i] -= j;
            }
            if(num[i] > 0){
                Q[++lx].c = val[i]*num[i];
                Q[lx].v = num[i]*j;
            }
        }
        return 0;
    }
    View Code

    T6:分组背包

     sol:将组的类别看做转移的阶段,类比于01背包,但要注意颜色类别,体积,同一颜色物品三者顺序不能换.

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    #define e exit(0)
    #define re register
    int V,n,c,f[1001],vis[101],len[101];
    struct BP{int cost,v;}Q[101][1001];
    inline int fd(){
        int s=1,t=0;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    int main()
    {
        freopen("kkk.in","r",stdin);
        freopen("kkk.out","w",stdout);
        V = fd(),n = fd();
        for(re int i=1;i<=n;++i){
            int cost = fd(),v = fd(),col = fd();
            Q[col][++len[col]].cost = cost;
            Q[col][len[col]].v = v;
            if(!vis[col]){
                vis[col] = 1;
                ++c;
            }
        }
        for(re int i=1;i<=c;++i){
            for(re int m=V;m>=0;--m)
                for(re int k=1;k<=len[i];++k){
                    //if(m < cost) continue;
                    int cost = Q[i][k].cost,v = Q[i][k].v;
                    if(m < cost) continue;
                    f[m] = max(f[m],f[m-cost]+v);
                }
        }
        printf("%d",f[V]);
        return 0;
    }
    View Code

    T7:coin

    sol:如果强行用二进制拆分写会超时,单调队列? 没试过.设f[i]表示i面值是否能被表示,used[j]表示f[j]变为1在i阶段最少用多少枚硬币.同时考虑到这个题的贪心性质.前i种硬币能拼成面值j只有两种情况:①在i-1及以前的阶段就使f[j]变成1.②在i阶段,存在f[j-a[i]]变为1,used[j-a[i]]<c[i].

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    using namespace std;
    #define e exit(0)
    #define re register
    const int maxn = 1e5+5;
    int n,m,a[maxn],b[maxn],f[maxn],used[maxn];
    inline int fd(){
        int s=1,t=0;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    int main()
    {
        while(1){
            n = fd(),m = fd();
            if(n == 0&&m == 0)
                break;
            for(re int i=1;i<=n;++i)
                a[i] = fd();
            for(re int i=1;i<=n;++i)
                b[i] = fd();
            f[0] = 1;
            for(re int i=1;i<=n;++i){
                for(re int j=0;j<=m;++j)
                    used[j] = 0;
                for(re int j=a[i];j<=m;++j)        
                    if(!f[j]&&f[j-a[i]]&&used[j-a[i]] < b[i])
                        f[j] = 1,used[j] = used[j-a[i]]+1;
            }
            int ans = 0;
            for(re int i=1;i<=m;++i)
                if(f[i]){
                    ans += 1;
                    f[i] = 0;
                }
            printf("%d
    ",ans);
        }
        return 0;
    }
    View Code

    T8:这里T3

    sol:经典完全背包找第k大问题.

    ⑤:期望DP

    E(x) = 总和/总方案数. E(x) = 单次方案概率*单次方案和.

    期望是满足线性递推关系的.

    T1:绿豆蛙的归宿

    sol:这个题首先要明白要逆推,应为终点到终点的期望距离肯定是0,也就是我们已知了终点的期望状态.那我们建反向图,做一遍拓扑排序,按照题意递推即可.

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<queue>
    using namespace std;
    #define e exit(0)
    #define D double
    #define re register
    const int maxn = 1e5+10;
    double f[maxn],ans;
    int n,m,cnt,head[maxn],du[maxn],degree[maxn];
    struct bian{int to,next,v;}len[maxn<<1];
    inline int fd(){
        int s=1,t=0;
        char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    void add(int from,int to,int v){
        len[++cnt].v = v;
        len[cnt].to = to;
        len[cnt].next = head[from];
        head[from] = cnt;
    }
    int main()
    {
        n = fd(),m = fd();
        for(re int i=1;i<=m;++i){
            int from = fd(),to = fd(),v = fd();
            add(to,from,v);
            ++du[from],++degree[from];
        }
        queue<int> q;
        q.push(n);
        while(q.size()){
            int now = q.front();
            q.pop();
            for(re int k=head[now];k;k=len[k].next){
                int to = len[k].to;
                f[to] += (f[now]+len[k].v)/degree[to];
                --du[to];
                if(du[to] == 0)
                    q.push(to);
            }
        }
        printf("%.2lf",f[1]);
        return 0;
    }
    View Code

    T2:涂色.

     ps:只会一个很暴力的办法.

    sol:首先设计f[s]为涂色涂成状态为s的概率,那么分k个阶段,每次选择两个点作为染色区间去暴力覆盖s的都新的状态s',注意s必须倒序(与01背包倒序原因一致),每次都成上n^2的逆元即可,最后累加状态s中1的个数 x f[s]即可.

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    #define e exit(0)
    #define re register
    #define LL long long
    const LL mod = 998244353;
    const LL maxn = (1<<16);
    LL n,k,ans,Maxn,Ins,f[maxn]; 
    inline LL fd(){
        LL s=1,t=0;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
        return s*t;
    }
    LL qsm(LL x,LL y){
        LL base = 1;
        while(y){
            if(y&1) base = (base%mod*x%mod)%mod;
            x = (x%mod*x%mod)%mod;
            y>>=1;
        }
        return base;
    }
    LL getnum(LL x){
        LL cnt = 0;
        while(x){
            ++cnt;
            x = x&(x-1);
        }
        return cnt;
    }
    int main()
    {
        freopen("paint.in","r",stdin);
        freopen("paint.out","w",stdout);
        n = fd(),k = fd();
        Maxn = (1<<n);
        f[0] = 1,Ins = qsm(n*n,mod-2);
        for(re LL i=1;i<=k;++i)
            for(re LL sta=Maxn;sta>=0;--sta){
                LL ret = f[sta];
                for(re LL a=0;a<n;++a)
                    for(re LL b=0;b<n;++b){
                        LL x = a,y = b;
                        if(x > y) swap(x,y);
                        LL s1 = (1<<x)-1,s2 = (1<<y+1)-1,s3,s4;
                        s3 = (s1^s2),s4 = (sta|s3);
                        f[s4] = (f[s4]%mod+(ret*Ins%mod)%mod)%mod;
                    }
            }
        for(re LL sta=0;sta<Maxn;++sta)
            ans = (ans%mod+(getnum(sta)%mod*f[sta]%mod)%mod)%mod;
        printf("%lld",ans);
        return 0;
    }
    View Code
  • 相关阅读:
    机器学习-分类算法-决策树,随机森林10
    机器学习-分类算法-模型选择与调优09
    机器学习-分类算法-精确率和召回率08
    机器学习-分类算法-朴素贝叶斯算法07
    机器学习-分类算法-K-近邻算法06
    java读取自定义配置文件并引用
    kafka发布消息报错LEADER_NOT_AVAILABLE
    Kettle位置参数(Argument)、命名参数(Parameter)、变量(Variable)
    kettle里的两个参数和一个变量
    如何在命令行下运行kettle的作业(job)和转换(transform)
  • 原文地址:https://www.cnblogs.com/xqysckt/p/11850999.html
Copyright © 2020-2023  润新知