• 状压dp


    状压dp

    当题目中的某一项的值极小(小于等于(20))时考虑使用状压(dp)

    状压(dp)是二维时一般是

    (dp[i][s])表示进行到第(i)步且第(i)步的状态为(s)时的(dp)

    状压(dp)由于是二进制,多与位运算的操作有关

    以下是一些常见的位运算操作:

    (1.s&(1<<i)) 判断第(i)位是否为(1)

    (2.s=s | (1<<i))把第i位设为(1)

    (3.s=s&() ~ ((1<<i)))把第(i)位设为(0)

    (4.s=s)^((1<<i))把第(i)位的值取反

    (5.s=s&(s-1))(s)二进制下最靠右的一个(1)去掉

    (6.for(s0=s;s0;s0=(s0-1)&s))枚举(s)的子集

    (7.x&-x)取出最低位的(1)

    例题

    互不侵犯 洛谷P1896

    (f[i][k][s])表示前(i)行放了(k)个国王,当前行状态为s的方案数

    #include <iostream>
    #include <cstdio>
    using namespace std;
    int n,King,tot=0;
    int s[1050],pd[1050];
    int num[15];
    long long ans=0;
    long long f[10][105][1050];
    void Deal_first()
    {
        for(int i=0;i<(1<<n);i++)
        {
            int cnt=0,x=i;
            pd[i]=1;
            while(x!=0)
            {
                num[++cnt]=x%2;
                x/=2;
            }
            for(int j=1;j<cnt;j++)
            {
                if(num[j]==num[j+1]&&num[j]==1)
                {
                    pd[i]=0;break;
                }
                if(num[j]==1)s[i]++;
            }
            if(pd[i]==1)if(num[cnt]==1)s[i]++;
        }
    }
    int main()
    {
        scanf("%d%d",&n,&King);tot=(1<<n)-1;
        Deal_first();
        for(int i=0;i<=tot;i++)if(pd[i]==1)f[1][s[i]][i]=1;
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<=tot;j++)
            {
                for(int k=0;k<=tot;k++)
                {
                    if(!pd[j])continue;if(!pd[k])continue;
                    if(((j|(j<<1)|(j>>1))&k)!=0)continue;
                    for(int t=King;t>=s[j];t--)f[i][t][j]+=f[i-1][t-s[j]][k];
                }
            }
        }
        for(int i=0;i<=tot;i++)
        {
            if(pd[i])ans+=f[n][King][i];
        }
        printf("%lld",ans);
        return 0;
    }
    

    愤怒的小鸟 洛谷P2831

    显然,因为原点加两个点即可确定抛物线,故抛物线的数量最多为(n^2)条,又因为(n<=18),

    故考虑用状压(dp),枚举抛物线,复杂度为(O(T imes n^2 imes2^n))

    但考虑到每个猪都要被打,所以可以从小到大枚举(S),每次每次打掉编号最小的还没消灭的猪,
    由于包含该猪的抛物线只有(O(n))种,所以时间复杂度为(O(n*2^n))

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <cstring>
    using namespace std;
    #define lld long double
    #define eps 1e-8
    int dp[1<<19],line[20][20];
    lld x[20],y[20];
    int n,m;
    int lowbit(int x)
    {
        return x&(-x);
    }
    int main()
    {
        int t;scanf("%d",&t);
        while(t--)
        {
        scanf("%d%d",&n,&m);
        lld z=eps;
        memset(dp,0x3f3f3f3f,sizeof(dp));
        memset(line,0,sizeof(line));
        dp[0]=0;
        for(int i=1;i<=n;i++)
        {
            //scanf("%lf%lf",&x[i],&y[i]);//double用这个
            cin>>x[i]>>y[i];//long double用这个
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(fabs(x[i]-x[j])<eps)continue;
                lld a=y[j]/(x[j]*(x[j]-x[i]))-y[i]/(x[i]*(x[j]-x[i]));
                lld b=(y[i]-a*x[i]*x[i])/x[i];
                if(a>-eps)continue;
                for(int k=1;k<=n;k++)
                    if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<eps)line[i][j]|=(1<<(k-1));
            }
        }
       for(int i=0;i<(1<<n);i++)
        {
            int x=-1;
            for(int j=1;j<=18;j++)
            {
                if((i&(1<<(j-1)))==0)
                {
                    x=j;break;
                }
            }
            if(x==-1)continue;
            dp[i|(1<<(x-1))]=min(dp[i|(1<<(x-1))],dp[i]+1);
            for(int j=1;j<=n;j++)dp[i|line[x][j]]=min(dp[i|line[x][j]],dp[i]+1);
        }
        printf("%d
    ",dp[(1<<n)-1]);
        }
        return 0;
    }
    
    

    玉米田

    题目链接

    题意:一块(n imes m)的牧场,有的方格可以种草,有的不能种,且任意种草的方格不能有公共边,求方案数

    (1<=n,m<=12)

    (m)极小,故可以用状压(dp)
    (f[i][j])表示前i行符合条件且第(i)行的状态为(j)的方案数
    预处理(cnt[i])表示在不管其它行的情况下第(i)行符合条件的状态数,(g[i][j])表示第i行符合条件的第j个状态
    在转移时需满足(g[i-1][j]&g[i][k]==0)

    #include <iostream>
    #include <cstdio>
    using namespace std;
    #define mol 100000000
    int n,m;
    int a[15][15];
    int h[15],cnt[15];
    int g[15][1<<15],f[15][1<<15];
    void read()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                scanf("%d",&a[i][j]);
            }
        }
        for(int i=1;i<=n;i++)
        {
            int sum=0;
            for(int j=1;j<=m;j++)
            {
                sum=sum*2+a[i][j];
            }
            h[i]=sum;
        }
    }
    void Deal_first()
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<=(1<<m)-1;j++)
            {
                  int pd=1;
                  if((j|h[i])==h[i])
                  {
                      if(((j&(j<<1))==0)&&((j&(j>>1))==0))pd=0;
                      if(!pd)g[i][++cnt[i]]=j;
                  }
            }
        }
        for(int i=1;i<=cnt[1];i++)f[1][g[1][i]]=1;
    }
    int main()
    {
        read();
        Deal_first();
        for(int i=2;i<=n;i++)
        {
            for(int j=1;j<=cnt[i-1];j++)
            {
                for(int k=1;k<=cnt[i];k++)
                {
                    if((g[i-1][j]&g[i][k])==0)
                    {
                        f[i][g[i][k]]=(f[i][g[i][k]]+f[i-1][g[i-1][j]])%mol;
                    }
                }
            }
        }
        int ans=0;
        for(int i=1;i<=cnt[n];i++)ans=(ans+f[n][g[n][i]])%mol;
        printf("%d
    ",ans);
        return 0;
    }
    

    炮兵阵地

    题目链接

    题意:在(n imes m)的网格地图中部署炮兵部队,只能在平原地形上部署,每支炮兵部队能攻击沿横向左右各两格,沿纵向上下各两格,求最多能部署多少支炮兵部队。

    玉米田的变式题。

    因为前面两行都会对当前行产生影响,故(dp)要多记录一维,但是(f[110][1024][1024])会炸空间

    但发现每一行合法的状态最多(59)种,故只需用(f[110][70][70])来记录

    预处理(cnt[i])表示在不管其它行的情况下第i行符合条件的状态数,(g[i][j])表示第i行符合条件的第j个状态

    我一开始把状态编号和状态搞混了,调错调了半天。

    #include <iostream>
    #include <cstdio>
    using namespace std;
    int n,m;
    char ch[110][15];
    int h[110],cnt[110];
    int g[110][1<<12];
    int num[1<<12]={};
    int f[110][70][70];
    void read()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)scanf("%s",ch[i]+1);
    }
    void Deal_first()
    {
        for(int i=1;i<=n;i++)
        {
            int sum=0;
            for(int j=1;j<=m;j++)
            {
                if(ch[i][j]=='P')sum=sum*2+1;
                else sum=sum*2;
            }
            h[i]=sum;
        }
        for(int i=1;i<=(1<<m)-1;i++)
        {
            int cnt=0;
            for(int j=1;j<=m;j++)
            {
                if((i&(1<<(j-1)))!=0)cnt++;
            }
            num[i]=cnt;
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<=(1<<m)-1;j++)
            {
                if((j|h[i])==h[i])//
                {
                    if((((j<<2)&j)==0)&&(((j<<1)&j)==0)&&(((j>>2)&j)==0)&&(((j>>1)&j)==0))g[i][++cnt[i]]=j;
                }
            }
        }
        for(int i=1;i<=cnt[1];i++)
        {
            for(int j=1;j<=cnt[2];j++)
            {
                if((g[1][i]&g[2][j])==0)f[2][j][i]=num[g[1][i]]+num[g[2][j]];
            }
        }
    }
    int main()
    {
        read();
        Deal_first();
        for(int i=3;i<=n;i++)
        {
            for(int j=1;j<=cnt[i];j++)
            {
                for(int k=1;k<=cnt[i-1];k++)
                {
                    if((g[i][j]&g[i-1][k])==0)
                    {
                        for(int t=1;t<=cnt[i-2];t++)
                        {
                            if(((g[i][j]&g[i-2][t])==0)&&((g[i-1][k]&g[i-2][t])==0))
                            {
                                f[i][j][k]=max(f[i][j][k],f[i-1][k][t]+num[g[i][j]]);
                            }
                        }
                    }
                }
            }
        }
        int ans=0;
        for(int j=1;j<=cnt[n];j++)
        {
            for(int k=1;k<=cnt[n-1];k++)
            {
                if((g[n][j]&g[n-1][k])==0)
                {
                    ans=max(ans,f[n][j][k]);
                }
            }
        }
        printf("%d
    ",ans);
        return 0;
    }
    

    动物园

    题目链接

    题意:

    动物园的围栏呈环状排列

    每个小朋友可以看到从他所在位置开始数的连续5个围栏,每个小朋友有他害怕或喜欢的动物。

    当下面两处情况之一发生时,小朋友就会高兴:

    • 至少有一个他害怕的动物被移走
    • 至少有一个他喜欢的动物没被移走

    你可以选择去移走一些围栏,使得最多的小朋友高兴

    围栏数为(N (1≤N≤10 000)),小朋友的个数为(C (1≤C≤50 000))

    经过观察发现围栏数极小,故可以用状压(dp)

    (f[i][j])表示第i个小朋友可以看到的动物被移走的状态为(j)

    预处理(s[i][j])表示第i个位置的所有小朋友可以看到的动物被移走的状态为j时会高兴的小朋友数量

    先枚举第n个小朋友的状态,再进行(dp)

    (dp)方程为

            for(int j=1;j<=n;j++)
            {
                for(int k=0;k<32;k++)
                f[j][k]=max(f[j-1][(k&15)<<1],f[j-1][((k&15)<<1)|1])+s[j][k];
            }
    

    整道题的代码:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    int n,m;
    int s[10100][35];
    int f[10100][35];
    int ans=0;
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++)
        {
            int e,f,l;scanf("%d%d%d",&e,&f,&l);
            int x=0,y=0;
            for(int j=1;j<=f;j++)
            {
                int xx;scanf("%d",&xx);
                x=x|(1<<((xx-e+n)%n));
            }
            for(int j=1;j<=l;j++)
            {
                int xx;scanf("%d",&xx);
                y=y|(1<<((xx-e+n)%n));
            }
            for(int j=0;j<32;j++)
            {
                s[e][j]+=((x&j)||(y&((~j)&31)));
            }
        }
        for(int i=0;i<32;i++)
        {
            memset(f,-0x3f3f3f3f,sizeof(f));
            f[0][i]=0;
            for(int j=1;j<=n;j++)
            {
                for(int k=0;k<32;k++)f[j][k]=max(f[j-1][(k&15)<<1],f[j-1][((k&15)<<1)|1])+s[j][k];
            }
            ans=max(ans,f[n][i]);
        }
        printf("%d
    ",ans);getchar();
        return 0;
    }
    

    学校食堂

    题目链接

    同学们排队在食堂吃饭,每个同学想吃的菜都有一个特定的口味值T[i],每个同学的菜所需的时间为T[i]^T[j](T[j]表示食堂做的前一道菜的口味值),每个同学有一定的忍耐值B[i],表示他最多允许他身后的B[i]个人先拿到饭菜。

    求在满足所有人的忍耐值的前提下,学校食堂做完这些菜最少需要多少时间。

    对于100%的数据,满足(1 ≤ N ≤ 1,000,0 ≤ Ti ≤ 1,000,0 ≤ Bi ≤ 7。)

    我们通过观察发现(B[i])的值极小,考虑状压(dp)

    这道题最大的一个难点就是(dp)方程怎么列

    首先我们考虑至少有一维是[i]且前i-1个人的菜已经做完了,肯定还有一维[j]用于状态压缩表示这个人和他身后的B[i]个人拿到菜的情况,又因为做菜时间与上一个拿到饭菜的人有关,所以再记录一维([k])表示上一个拿到饭菜的人相对第i个人的位置,经过分析可得k的范围是([-8,7])。(作为数组下标时需整体加8)

    所以(f[i][j][k])表示前i-1个人已经拿到饭菜了,第(i)个人到第(i+B[i])个人拿到饭菜的情况为(j),上一个拿到饭菜的人为第(i+k)个人

    考虑状态方程如何转移:

    如果(j&1==1),那么(f[i][j][k])可以等价地转移到(f[i+1][j>>1][k-1])

    如果(j&1==0),枚举下一个拿到饭菜的人,注意用(B[i])来限制枚举的范围

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    int n;
    int T[1010],B[1010];
    int f[1070][1<<10][20];
    int main()
    {
        int Case;scanf("%d",&Case);
        while(Case--)
        {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)scanf("%d%d",&T[i],&B[i]);
        memset(f,0x3f3f3f3f,sizeof(f));
        f[1][0][7]=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<=(1<<8)-1;j++)
            {
                for(int k=-8;k<=7;k++)
                {
                    if(f[i][j][k+8]>=1e9)continue;
                    if(j&1)f[i+1][j>>1][k+7]=min(f[i+1][j>>1][k+7],f[i][j][k+8]);
                    else
                    {
                        int cnt=1e9;
                        for(int t=0;t<=7;t++)
                        {
                            if(i+t>cnt)break;
                            if((j>>t)&1)continue;
                            cnt=min(cnt,i+t+B[i+t]);
                            f[i][j|(1<<t)][t+8]=min(f[i][j|(1<<t)][t+8],f[i][j][k+8]+((i+k>0)?(T[i+k]^T[i+t]):0));
                        }
                    }
                }
            }
        }
        int ans=1e9;
        for(int i=-8;i<=0;i++)ans=min(ans,f[n+1][0][i+8]);
        printf("%d
    ",ans);
        }
        return 0;
    }
    
    
  • 相关阅读:
    java线程学习之volatile关键字
    java线程学习之yield方法
    java线程学习之join方法
    小程序hideTarBar隐藏TabBar后,获取windowHeight不准确问题
    canvas等base64格式上传到服务端直传到oss
    服务器关于node的注意事项
    node.js连接本地数据库
    小程序(mpvue框架)的总结
    git代码的注意
    js里的实用小技巧
  • 原文地址:https://www.cnblogs.com/Akaina/p/11374131.html
Copyright © 2020-2023  润新知