状态压缩好题记录

题目链接:https://ac.nowcoder.com/acm/contest/28259/A

题意:在一个n*n的棋盘中,放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上 左下右上右下八个方向上附近的各一个格子,共8个格子。

#include<bits/stdc++.h>
using namespace std;
vector<int>st;
vector<int>num;
int n,k;
long long f[12][100][1000];//第一维表示行,第二维表示当前放置将军的总个数,第三维表示当前行放置将军的状态
int calc(int x)//统计每个状态含有1的个数
{
    int cnt=0;
    while(x)
    {
       if(x&1)cnt++;
        x>>=1;
    }
    return cnt;
}
int main()
{
    cin>>n>>k;
    for(int i=0;i<(1<<n);i++)//将每种合法状态保存起来,节约时间
    {
         if(i&(i<<1))continue;
         st.push_back(i);
         num.push_back(calc(i));
    } 
   f[0][0][0]=1;//给第一行的所有和法状态赋值成1,减小了我们的代码量
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=k;j++)//枚举当前放置将军的总个数
        {
                for(int cur=0;cur<st.size();cur++)//枚举当前行放置将军的状态
                {
                    if(num[cur]>j)continue;//如果当前行的将军个数大于当前放置将军的总个数肯定就是不合法的,
                    for(int last=0;last<st.size();last++)//枚举上一行放置将军的状态
                    {
                        if(st[cur]&st[last])continue;
                        if((st[cur]<<1)&st[last])continue;
                        if(st[last]&(st[cur]>>1))continue;
                        f[i][j][cur]+=f[i-1][j-num[cur]][last];
                     }
                }
        }
    }
    long long sum=0;
        for(int cur=0;cur<st.size();cur++)
            sum+=f[n][k][cur];
    cout<<sum<<endl;
    return 0;
}

思路:关于赋初值的问题,我们只需给f[0] [0] [0] =1,将其初值赋成1即可,这样我们就可以将第一行的合法状态全部赋值成1.因为当第一行放x个时,因为是第一行所以前面不能放将军,所以只有当本行放的将军个数与当前总共放的将军个数相等的时候才是一种合法的状态,所以很自然就给第一行赋好了初值。后面的状态就可以由第一行的状态转移过去。

题目链接:https://www.luogu.com.cn/problem/P1879

题意:一块n x m的牧场,要求你种草,并且有些方格不能种草,并且两块草不能相邻,问有多少种种草的方案。

#include<bits/stdc++.h>
using namespace std;
long long f[14][(1<<12)],mp[19];
int n,m;
int mod=100000000;
vector<int>st;
int main()
{
    memset(f,0,sizeof(f));
     cin>>n>>m;
     for(int i=1;i<=n;i++)//将图存起来,1表示不能种草,0表示可以
     {
        for(int j=1;j<=m;j++)
        {
            int cnt=0;
            cin>>cnt;
            if(cnt==0)
            {
                mp[i]|=(1<<(j-1));//将不能种草的那块地变成1
            }
        }
     }
     for(int i=0;i<(1<<m);i++)//将所有合法状态存起来
     {
         if(((i<<1)&i)||((i>>1)&i))continue;//草与草之间不能相邻
         st.push_back(i);
     }
     f[0][0]=1;
     for(int i=1;i<=n;i++)
     {
        for(int j=0;j<st.size();j++)//当前行的状态
        {
            if(st[j]&mp[i])continue;//保证没在不能种草的地方种草
            for(int last=0;last<st.size();last++)//上一行的状态
            {
                if(st[last]&mp[i-1])continue;//保证上一行没在不能种草的地方种草
                if(st[last]&st[j])continue;//两块草地不能相邻
                f[i][st[j]]=(f[i][st[j]]+f[i-1][st[last]])%mod;
            }
        }
     }
     long long ans=0;
     for(int i=0;i<(1<<m);i++)
     {
        ans+=f[n][i];
        ans%=mod;
     }
     cout<<ans<<endl;
     system("pause");
     return 0;
}

思路:因为草地之间不能相邻,所以我们可以将所有状态左移一位与上当前状态看是否为0,为0就说明没有相邻的,右移也一样。我们在存图的时候将不能种草的地方存为1,能种草的地方存为0,这样当前种草的状态与上地图就为0了,方便我们判断是否在不能种草的地方种了草。

题目链接:https://ac.nowcoder.com/acm/contest/28259/B

#include<bits/stdc++.h>
using namespace std;
int n,m;
int f[109][80][80];//第一维表示行,二维表示当前行放置炮兵的状态,三维表示上一行的状态
int mp[109];
int ans=0;
vector<int>st;
vector<int>num;
int calc(int x)
{
   int cnt=0;
    while(x)
    {
        if(x&1)cnt++;
        x>>=1;
    }
    return cnt;
}

int main()
{
    cin>>n>>m;
    for(int i=0;i<(1<<m);i++)
    {
        if(i&(i<<1))continue;
        if(i&(i<<2))continue;
        st.push_back(i);
        num.push_back(calc(i));
    }
    getchar();//在输入字符前一定要记得将空格和回车吃掉
    for(int i=1;i<=n;i++)
    {
         for(int j=1;j<=m;j++)
         {
             char ch;
             scanf("%c",&ch);
             if(ch=='H')mp[i]|=(1<<(j-1));//将该行的山地和平原表示成一个01串,山地为1平原为0
         }
         getchar();//吃回车
    }
    for(int i=1;i<=n;i++)
    {
         for(int j=0;j<st.size();j++)//枚举当前行放置炮兵的状态
         {
             int cur=st[j];
             if(cur&mp[i])continue;
             for(int k=0;k<st.size();k++)//枚举上一行放置炮兵的状态
             {
                 int last=st[k];
                 if(cur&last)continue;
                 for(int p=0;p<st.size();p++)//枚举上上行放置炮兵的状态
                 {
                      if(st[p]&last||st[p]&cur)continue;
                     f[i][j][k]=max(f[i][j][k],f[i-1][k][p]+num[j]);//题目问的时最多能放多少炮兵部队
                     ans=max(ans,f[i][j][k]);
                 }
                 
             }
             
         }
    }
    cout<<ans;
return 0;
}

思路:我们只需将前两行所有与当前行和法的状态放置最大炮兵数找出来,然后再加上当前行状态为j的炮兵数,就是当前行状态为j的最优解。

题目链接:https://ac.nowcoder.com/acm/problem/51189

题意:1x2 或 2x1 的方块覆盖矩形,多少种方案,矩阵大小为n*m.

#include<bits/stdc++.h>
using namespace std;
long long f[13][(1<<13)];
long long n,m;
long long st[1<<13];
int main()
{
 while(cin>>n>>m)
 {
     if(n==0&&m==0)break;
     for(long long i=0;i<(1<<m);i++)//将所有状态中,连续的0个数为偶数个的串选出,方便后面的判断
    {
        int cnt=0;
        for(int j=0;j<m;j++)
        {
            if(i&(1<<j))
            {
                if(cnt)break;
            }
            else cnt^=1;//取反
        }
        if(cnt)st[i]=0;
        else st[i]=1;
    }
    f[0][0]=1;
    for(int i=1;i<=n;i++)//枚举行
    {
          for(int j=0;j<(1<<m);j++)//枚举当前行的状态
          {
              f[i][j]=0;//将上次的内容清空
                for(int k=0;k<(1<<m);k++)//枚举上一行的状态
                {
                    if(((k&j)==0)&&st[j|k])//当前行和上一行合法
                    {
                          f[i][j]+=f[i-1][k];
                    }
                }
          }
    }
    cout<<f[n][0]<<endl;//第n行全为0即填满了
     
 }
    
   return 0;
}

思路:我们定义的状态为竖着放上面为10,横着放为00  。这样我们就可以很好的知道合法的状态了,即当前行的状态与上上一行的状态为0   st[cur]&st[last]=0    ,我们还需保证当前行的状态或上上一行的状态的01串中连续的0的个数为偶数个。   关于赋初值,我们只需对f[0] [0]=1即可。当我们枚举当前行的状态时,只要当前行是合法的,那么当k=0时就会给第一行当前合法的状态赋个初值1.

题目链接:https://ac.nowcoder.com/acm/problem/15832

题意:有N个球,每两个球相互碰撞被撞的球就会消失并产生一些能量,给出每一个球去碰其他球会产生的能量。要你求出碰撞产生的最大能量是多少。

#include<bits/stdc++.h>
#include<bits/stdc++.h>
using namespace std;
int dp[(1<<10)+10];
int a[30][30];
int n;
int main()
{
    while(cin>>n)
    {
        if(n==0)break;
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<n;j++)
            cin>>a[i][j];//输入球与球之间碰撞产生能量的关系
        }
          memset(dp,0,sizeof(dp));
    for(int i=0;i<(1<<n);i++)//枚举之前的状态
    {
        for(int j=0;j<n;j++)//枚举被撞的球
        {
            if(i&(1<<j))continue;//选出一个0,即还存在
            for(int k=0;k<n;k++)//枚举撞的球
            {
                
                if(i&(1<<k)||k==j)continue;//撞的球必须是当下没消失的
                dp[i|(1<<j)]=max(dp[i|(1<<j)],dp[i]+a[k][j]);
                //可以求出当前被撞的球可以产生的最大能量
            }
        }
    }
    int ans=0;
    for(int i=0;i<n;i++)
    {
        ans=max(ans,dp[(1<<n)-1-(1<<i)]);//将最后剩下一个的情况选出一个最大的出来,最后肯定会剩下一个
    }
    cout<<ans<<endl;
    }
  
    return 0;
}

思路:对于某一状态,我们不需要考虑之前是怎么碰撞的,我们只需考虑剩下没有消失的球怎么碰撞产生的能量最大即可。我们的状态定义为 0 表示还存在,1 表示消失了。

题目链接:https://www.luogu.com.cn/problem/P1433

#include<bits/stdc++.h>
using namespace std;
double x[20],y[20],a[20][20];
double f[20][(1<<17)];
int n;
double calc(int w,int v)//计算两点之间的距离
{
	return sqrt((x[w]-x[v])*(x[w]-x[v])+(y[w]-y[v])*(y[w]-y[v]));
}

int main()
{
	cin>>n;
	x[0]=0;y[0]=0;
	memset(f,127,sizeof(f));//将f赋值成无穷大
	double ans=f[0][0];
	for(int i=1;i<=n;i++)
	{
		cin>>x[i]>>y[i];
	}
	for(int i=0;i<=n;i++)
	{
		for(int j=1+i;j<=n;j++)
		{
			a[i][j]=calc(i,j);
			a[j][i]=a[i][j];
		}
	}
	for(int i=1;i<=n;i++)
	{
		f[i][1<<(i-1)]=a[0][i];//初始化该点到原点的距离
	}
	for(int i=1;i<(1<<n);i++)//遍历所有的状态
	{
		for(int j=1;j<=n;j++)//枚举最后到达的那个点
		{
			if((i&(1<<(j-1)))==0)continue;//我们要的是已经走过的点,然后将到该点的最小值算出
			for(int k=1;k<=n;k++)//枚举是从哪个点到达最后的那个点的
			{
				if(k==j)continue;
				if((i&(1<<(k-1)))==0)continue;
				f[j][i]=min(f[j][i],f[k][i-(1<<(j-1))]+a[j][k]);
			}
		}
	}

	for(int i=1;i<=n;i++)
	{
		ans=min(ans,f[i][(1<<n)-1]);//将每个点是最后走的那个点的情况取个最小值就行了
	}
	printf("%.2lf",ans);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值