其他分享
首页 > 其他分享> > P1896 【[SCOI2005]互不侵犯】

P1896 【[SCOI2005]互不侵犯】

作者:互联网

这题主要分两个步骤:

Section A:

初始化每一行中国王互相不攻击的情况

Section B:

考虑列和列之间国王不攻击。

解释都在代码里:

#include<bits/stdc++.h>

#define N 15//行数 
#define M 1050//状态数 
#define K 105//国王数 
#define rd(x) x=read()

using namespace std;

typedef long long ll;

ll dp[N][M][K];
int n,m,cnt=0;

struct node 
{
    int s;
    int num;//1的个数,1代表国王放置  
}st[M];

inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch>'9'||ch<'0') { if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') { x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

void init()//预处理出每行的合法状态 
{
    int totS=(1<<n);
    for(int s=0;s<totS;s++) 
    {
        if(s&(s<<1))continue;//存在1相邻 
        int num=0;
        for(int i=0;i<n;i++)if(s&(1<<i))num++;//统计1的个数 
        if(num>m)continue;//1的个数超限 
        st[cnt].s=s,st[cnt++].num=num;//加入状态 
    }
}

void solve()
{
    memset(dp,0,sizeof(dp));
    dp[0][0][0]=1;
    for(int i=1;i<=n;i++)//枚举行 
        for(int j=0;j<cnt;j++)//枚举第i行状态 
            for(int l=0;l<cnt;l++) 
            {
                if(st[j].s&st[l].s)continue;//上一行同列冲突 
                if(st[j].s&(st[l].s<<1))continue;//上一行右上角冲突 
                if(st[j].s&(st[l].s>>1))continue;//上一行左上角冲突 
                for(int k=st[j].num;k<=m;k++)
                    dp[i][j][k]+=dp[i-1][l][k-st[j].num];
            }
}

int main()
{
    rd(n),rd(m);
    init(),solve();
    ll ans=0;
    for(int i=0;i<cnt;i++)ans+=dp[n][i][m];
    printf("%lld\n",ans);
    return 0;
}

附录:

因为这是一道状压DP的模板

而状压DP需要足够的位运算基础以减少代码量

这里给出常用的位运算操作(共19个)

1、去掉最后一位 例如:101101->10110

2、在最后加一个0 例如:101101->1011010

3、在最后面加一个1 例如:101101->1011011

4、把最后一位变成0 例如:101101->101100

5、把最后一位变成1 例如:101100->101101

6、最后一位取反 例如:101101->101100

7、把右数第k位变成1 例如:101001->101101,k=3

8、把右数第k位变成0 例如:101101->101001,k=3

9、右数第k位取反 例如:101001->101101,k=3

10、取末三位 例如:1101101->101

11、取末k位 例如:1101101->1101,k=4

12、取右数第k位 例如:1101101->1,k=4

13、把末尾k位变成1 例如:101001->101111,k=4

14、末k位取反 例如:101001->100110,k=4

15、把右边连续的1变成0 例如:100101111->100100000

16、把右边起第一个0变成1 例如:100101111->100111111

17、把右边起连续的0变成1 例如:110011000->110011111

18、取右边连续的1 例如:100101111->1111

19、去掉右边起第一个1的左边 例如:100101000->1000

code:

if(opt==1)x>>=1;//右移一位
else if(opt==2)x<<=1;//左移一位 
else if(opt==3)x<<=1,x|=1;//左移一位+1 
else if(opt==4)x|=1,x--;//强制把最后一位变为1后-1(这样就变为0呀) 
else if(opt==5)x|=1;//变为1 
else if(opt==6)x^=1;//1=0000……0001,异或时前面都不变,最后一位取反 
else if(opt==7){scanf("%d",&k);x|=(1<<(k-1));} 
else if(opt==8){scanf("%d",&k);x|=(1<<(k-1)),x-=(1<<(k-1));}
else if(opt==9){scanf("%d",&k);x^=(1<<(k-1));} //以上操作跟前面类似 
else if(opt==10)x%=8; 
else if(opt==11){scanf("%d",&k);x%=(1<<k);}//取模得后k位 
else if(opt==12){scanf("%d",&k);x&=((1<<(k-1)));x=x?1:0;}//如果结果不为0,那么那一位就为1 
else if(opt==13){scanf("%d",&k);x|=((1<<k)-1);} 
else if(opt==14){scanf("%d",&k);x^=((1<<k)-1);}//以上操作跟前面类似
else if(opt==15)x&=(x+1);
else if(opt==16)x|=(x+1);
else if(opt==17)x|=(x-1);
else if(opt==18)x++,x&=(-x),x--;
else x&=(-x);

标签:例如,101101,互不侵犯,int,P1896,num,SCOI2005,dp,101001
来源: https://www.cnblogs.com/Robin20050901/p/10786065.html