状压DP好题分享

#2064. 分裂
(一道只可意会不可言传的题目....)
首先我们最朴素的思想就是将原本的各个国家的面积全部合到一起然后依次拆开乘现在的国家的面积。这样的操作次数是n+m-2的。考虑怎么优化这个过程,比较显然的发现是若原来的某个国家的面积和现在的某个国家的面积相等的话,我们就不用考虑这个国家,直接合并其他国家即可。也就是说操作次数其实减少了2.我们尝试能不能将这个情况一般化,发现是可以的。发现如果有若干个国家原本的面积和和现在的若干个国家原本的面积和相等的话,我们完全可以将这若干个国家单独考虑,这样的话操作次数就减少了2。也就是说我们现在的任务是要将现在的国家和原本的国家进行分组,然后将现在的组合原本的组配对(面积和相等)。使得配对的组数足够多,找到最大的配对数。这之后的操作就比较玄学了,首先我们把之前的国土面积设成正的,把现在的国土面积设成负的。然后把现在和过去的状态放一起,用0,1表示当前这个国土面积选没选。这样之后我们就可以用dp写了。f[s]表示在s这个状态下最大的配对的组数。至于转移,任何一个当前状态下没有的国土都可以加入这个集合,然后根据和是否为0即可转移。真的只可意会不可言传.....

#include<bits/stdc++.h>
using namespace std;
const int N=11;
int n1,n2,a[N*2],f[1<<(N*2)],n,g[1<<(N*2)];
int main()
{
//    freopen("1.in","r",stdin);
    scanf("%d",&n1);
    for(int i=1;i<=n1;++i) scanf("%d",&a[i]);
    scanf("%d",&n2);
    for(int i=n1+1;i<=n1+n2;++i) 
    {
        scanf("%d",&a[i]);
        a[i]=-a[i];   
    }
    n=n1+n2;
    for(int i=0;i<=(1<<n)-1;++i) 
    {
        for(int j=1;j<=n;++j) if(i&(1<<j-1))
            g[i]+=a[j];
    }
    for(int i=0;i<=(1<<n)-1;++i)
    {
        for(int j=1;j<=n;++j)
        {
            if(!(i&(1<<j-1)))
            {
                if(g[i]+a[j]==0) f[i|(1<<j-1)]=max(f[i|(1<<j-1)],f[i]+1); 
                else f[i|(1<<j-1)]=max(f[i|(1<<j-1)],f[i]); 
            }
        }
    }
    printf("%d",n-2*f[(1<<n)-1]);
    return 0;
} 

#3195. [Jxoi2012]奇怪的道路
一道真的很不错的状压DP的题目。
首先我们看到题目就知道要设三维的dp,f[i][j][k]表示当前处理到点i,用了j条边,从i到i-9的点的奇偶性,可是发现到转移的时候,根本没法转移,发现转移顺序是乱的,无法有效处理任意两个城市之间的道路是多条的情况....
而这个题难,也就难在了它的状态设置上,我们发现当前点i对之前9个点的连边我们无法转移这个状态。既然如此,我们不妨再多加一维,f[i][j][k][l]表示当前处理到点i,用了j条边,i-8到i这9个点的连边的奇偶性,以及当前已经处理过了点i到第l个点的连边的方案数。这样的话我们的转移就行了。(真nm恶心...调死老子了...)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=32,P=1000000007;
int n,m,k; 
ll f[N][N][10][1<<9];
//f[i][j][l][s]表示前i个点用了j条边当前九个点的状态是s当前处理过了l条边的方案数 
int main()
{
//    freopen("1.in","r",stdin);
    scanf("%d%d%d",&n,&m,&k);
    f[2][0][0][0]=1;
    for(int i=2;i<=n;++i)
        for(int j=0;j<=m;++j) 
            for(int l=0;l<=min(k,i-1);++l)
            {
                for(int s=0;s<=(1<<(min(k,i-1)+1))-1;++s)
                {
                    if(l==min(k,i-1)) 
                    {
                        if((s&(1<<k))==0)
                        {
                            f[i+1][j][0][s<<1]=(f[i+1][j][0][s<<1]+f[i][j][l][s])%P;
                        }
                    }
                    else
                    {
                        for(int o=0;o<=m-j;++o)//枚举i对第l+1个点连的边数
                        {
                            int d=o%2,io=min(k,i-1);
                            int sd=s;
                            if(d) sd=s^(1<<io-l)^d;
                            f[i][j+o][l+1][sd]=(f[i][j+o][l+1][sd]+f[i][j][l][s])%P;
                        }    
                    }          
                }
            }
    printf("%lld",f[n][m][min(k,n-1)][0]);
    return 0;
} 
posted @ 2022-01-21 17:56  逆天峰  阅读(40)  评论(0编辑  收藏  举报
作者:逆天峰
出处:https://www.cnblogs.com/gcfer//