状态压缩刷题

所谓状态压缩,大多数就是用二进制01形式将状态表示出来,运用位运算完成状态的查看和转移;基本上数据范围是n<=15;

 

P4906 小奔关闹钟

这是很裸的状态压缩。我们要关闭所有的开关,但是开关是相连的;

有一个很好地条件是,开关最多能波及到两层;一个开关的变化,直接关联的会变化,间接变化的也会变,但是间接变化的不会再传递下去;

所以我们将每个开关关掉后的状态记录下来;

设二进制1是关闭状态,当状态变为0时更新答案即可;

要排除自己关自己的情况;

二进制枚举关哪一个就行了;

一个开关不需要关两遍及以上,相当于没干什么;

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e6+10;
int res[maxn];
int a[30][30];
int n;
int ans=2147483647;
void dfs(int x,int now,int cnt)
{
    if(x==n+1)
    {
        if(now==0)
        {
            ans=min(ans,cnt);
        }
        return ;
    }
    
    dfs(x+1,now,cnt);
    now^=res[x];
    dfs(x+1,now,cnt+1);
    now^=res[x];
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int m;
        scanf("%d",&m);
        for(int j=1;j<=m;j++)
        {
            int x;
            scanf("%d",&x);
            a[i][x]=1;
        }
    }
    for(int i=1;i<=n;i++)
    {
        res[i]^=(1<<i);
        for(int j=1;j<=n;j++)
        {
            if(a[i][j]&&i!=j)
            {
                res[i]^=(1<<j);
                for(int k=1;k<=n;k++)
                {
                    if(a[j][k]&&j!=k)
                    {
                        res[i]^=(1<<k);
                    }
                }
            }
        }
    }
    //int sum=0;
    int sum=(1<<(n+1))-2;
    /*for(int i=1;i<=n;i++)
    {
        sum+=1<<i;
    }*/
    //printf("%d\n",sum);
    dfs(1,sum,0);
    
    if(ans==2147483647) printf("Change an alarm clock,please!");
    else printf("%d",ans);
    return 0; 
    
}
View Code

 

 

 

P2622 关灯问题II

这道题的状态比上面的题多一点点,但是基本类比;不同的是我们这次用DP转移。

设f[i]为当前为i状态时,最少关灯次数;

二进制数1表示灯开着,f[0]为答案;

每次判断每个按键能控制的灯,按完后的状态为now,从按之前的转移过来;

(1<<(k-1)&now)表示当前第k个灯还亮着;

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2e5+10;

int n,m;
int a[120][120];
int f[maxn];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=n;j++)
        {
            scanf("%d",&a[i][j]);
        }
    }
    
    memset(f,0x7f,sizeof(f));
    f[(1<<n)-1]=0;
    for(int i=(1<<n)-1;i>=0;i--)
    {
        
        for(int j=1;j<=m;j++)
        {
            int now=i;
            for(int k=1;k<=n;k++)
            {
                if(a[j][k]==0) continue;
                if(a[j][k]==1&&((1<<(k-1)&now))) now^=(1<<(k-1));
                else if(a[j][k]==-1&&!((1<<(k-1))&now)) now^=1<<(k-1);
            }
            f[now]=min(f[now],f[i]+1);
        }
    }
    if(f[0]==2139062143) printf("-1");
    else printf("%d",f[0]);
    return 0;
}
View Code

 

 

P2915 [USACO08NOV]奶牛混合起来Mixed Up Cows

 

我们需要的是方案数,DP跑不了;

怎么设计状态?

设f[i][j]为第i头牛再队尾时,情况为j时的合法方案数;

二进制1表示牛在队列里;

我们枚举哪头牛再队尾的情况;

再枚举哪头牛再他的前面,状态直接搬运过来;

 

初始化 队列中只有一头牛的方案数为1;

枚举时判断混乱条件;

最后的答案是加和;

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
ll f[20][maxn];
int n,x;
int a[20];

ll ans;
int main()
{
    scanf("%d%d",&n,&x);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    
    for(int i=1;i<=n;i++)
    {
        f[i][1<<(i-1)]=1;
    }
    for(int i=1;i<1<<n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(f[j][i]) continue;
            if(!(i&(1<<(j-1)))) continue;
            int now=i^(1<<(j-1));
            for(int k=1;k<=n;k++)
            {
                if(j==k) continue;
                if(!(now&(1<<(k-1)))||abs(a[j]-a[k])<=x) continue;
                f[j][i]+=f[k][now];
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        ans+=f[i][(1<<n)-1];
    }
    printf("%lld",ans);
    return 0;
}
View Code

 

 

 

P2473 [SCOI2008]奖励关

这个题是状态压缩+期望;

将每个物品的前提条件状态压缩存起来,当前状态能吃的时候比较一下即可;

设f[i][j]表示在第1轮到第i1轮内宝物是否取过的状态为j,第i轮到第K轮的最大期望得分。

 

但是我们需要逆推,因为正着枚举有些状态是并没有被算出来的;

如果当前状态能选当前物品

f[i][j]+=max(f[i+1][j],f[i+1][j|(1<<(k-1))]+(dd)p[k]);

下一轮是要选k的,才能加上p[k];

每次都要/n,因为选k的概率是1/n;

 

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef double dd;

dd f[110][1<<15];

int k,n;
int p[20];
int res[20];

int main()
{
    scanf("%d%d",&k,&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&p[i]);
        int x;
        while(scanf("%d",&x)&&x)
        {
            res[i]|=1<<(x-1);
        }
    }
    
    for(int i=k;i>=1;i--)
    {
        for(int j=0;j<(1<<n);j++)
        {
            for(int k=1;k<=n;k++)
            {
                if((res[k]&j)==res[k])
                {
                    f[i][j]+=max(f[i+1][j],f[i+1][j|(1<<(k-1))]+(dd)p[k]);
                }
                else f[i][j]+=f[i+1][j];
            }
            f[i][j]/=(dd)n;
        }
    }
    
    printf("%.6lf",f[1][0]);
    return 0;
}
View Code

 

posted @ 2019-10-12 11:21  AiRomance  阅读(161)  评论(0编辑  收藏  举报