洛谷题单【动态规划5】状态压缩动态规划

P1441 砝码称重

思路

dfs+dp
来自洛谷题解

实现

#include <bits/stdc++.h>
using namespace std;
const int maxn=22;
const int maxm=2010;
int n,m;
int a[maxn];
int ans,ret,tot;
bool isdrop[maxn],f[maxm];
void dp()
{
	memset(f,0,sizeof(f));
	f[0] = 1;
	ans = 0,tot = 0;
	for(int i=1;i<=n;i++)
	{
		if(isdrop[i]) continue;
		for(int j=tot;j>=0;j--)
		{
			if(f[j]&&!f[j+a[i]])
			{
				f[j+a[i]] = 1;
				ans++;
			}
		}
		tot+=a[i];
	}
	ret = max(ans,ret);
}
void dfs(int i,int drop)
{
	if(drop>m) return ; 
	if(i==n)
	{
		if(drop==m)
		dp();
		return ;
	}
	dfs(i+1,drop);
	isdrop[i+1] = 1;
	dfs(i+1,drop+1);
	isdrop[i+1] = 0;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	dfs(0,0);
	cout<<ret;
}

P1896 [SCOI2005] 互不侵犯

思路

很适合用来学状压DP
f [ i ] [ j ] [ s ] f[i][j][s] f[i][j][s]表示第 i i i行的状态为 j j j,一共摆了 k k k个国王时所有的状态总数。
j j j需要从 1 1 1开始,如果是 0 0 0开始的话会造成重复统计哈

  • 并不是所有的 j j j都是可行的,首先需要预处理出所有可行的 j j j和其对应的国王数
  • j j j k k k存在制约关系
  • 状态转移方程为对所有可行的 k k k: d p [ i ] [ j ] [ s ] + = d p [ i − 1 ] [ k ] [ s − c o u n t [ j ] ] dp[i][j][s] += dp[i-1][k][s-count[j]] dp[i][j][s]+=dp[i1][k][scount[j]]

实现

#include <bits/stdc++.h> 
using namespace std;
int state[2000],count1[2000];  
//state[i]表示第i种状态的数字表示,count1[i]表示第i种状态有多少个二进制1
int cnt=0;
int n,m;
long long f[10][2000][100]={0};
void dfs(int now,int sum,int i) 
//i表示当前处理到第几个各自,now表示当前表示状态的值,sum表示二进制1的个数
{
    if(i>=n)
    {
        state[++cnt]=now;
        count1[cnt]=sum;
        return;
    }
    dfs(now,sum,i+1);//不用第i个
    dfs(now+(1<<i),sum+1,i+2);//用第i个 用第i个就不能选第i+1个了
}
int main()
{
    scanf("%d%d",&n,&m);
    dfs(0,0,0);
    for(int i=1;i<=cnt;i++)f[1][i][count1[i]]=1; //第一层的所有状态均有1种情况的
    for(int i=2;i<=n;i++) 
        for(int j=1;j<=cnt;j++)  //从1开始
            for(int k=1;k<=cnt;k++)//枚举i、j、k
            {
                if(state[j]&state[k])continue;
                if((state[j]<<1)&state[k])continue;
                if(state[j]&(state[k]<<1))continue;
                for(int s=m;s>=count1[j];s--)f[i][j][s]+=f[i-1][k][s-count1[j]];
            }
    long long ans=0;
    for(int i=1;i<=cnt;i++)ans+=f[n][i][m];
    printf("%lld",ans);
    return 0;
}

P3694 邦邦的大合唱站队

思路

  • f [ i ] f[i] f[i]表示某种状态下最少让多少偶像出列
  • n u m [ i ] num[i] num[i]表示第 i i i个团队在这里有多少人
  • c n t [ i ] [ j ] cnt[i][j] cnt[i][j]表示前 i i i个人里有多少个编号为 j j j的团队的人
    在这里插入图片描述

实现

#include <bits/stdc++.h> 
using namespace std;
const int maxn = 1<<22;
int n,m;
int f[maxn];
int num[25],cnt[100010][25];

int main()
{
	int t;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
    	scanf("%d",&t);
    	num[t]++;
    	for(int j=1;j<=m;j++)
    	cnt[i][j] = cnt[i-1][j];
    	cnt[i][t]++;
	}
	memset(f,0x3f,sizeof(f));
	f[0] = 0;
	for(int i=1;i<(1<<m);i++)
	{
		int len = 0;
		for(int j=1;j<=m;j++)
		{
			if(i & (1<<(j-1))) len+=num[j];
		}
		for(int j=1;j<=m;j++)
		{
			if(i&(1<<j-1))
			{
				f[i] = min(f[i],f[i^(1<<(j-1))]+num[j]-cnt[len][j]+cnt[len-num[j]][j]);
			}
		}
	}
	cout<<f[(1<<m) -1];
    return 0;
}

P2704 [NOI2001] 炮兵阵地

压两行的想法没有想出来
很不错的题解

P1879 [USACO06NOV]Corn Fields G

思路

  • 将农场的每一行转换为数字,其二进制表示每一列是否可种植
  • 预处理每一状态是否合理
  • f [ i ] [ j ] = ( f [ i ] [ j ] + f [ i − 1 ] [ k ] ) m o d p f[i][j]=(f[i][j]+f[i−1][k]) mod p f[i][j]=(f[i][j]+f[i1][k])modp 枚举所有不会冲突的 k k k
  • 最终答案是 ∑ f [ m ] [ j ] \sum{f[m][j]} f[m][j]

注意

  • 判断k是否可行时只需要 k & j k \&j k&j 0 0 0即可,不需要判断 k k k本身是否可行(可种植、无重叠边),因为如果 k k k本身不可行的话 f [ i − 1 ] [ k ] = = 0 f[i-1][k]==0 f[i1][k]==0,没有影响
  • 相等运算符的优先级高于逻辑运算符 所以 ( j & F [ i ] ) = = j (j\&F[i])==j (j&F[i])==j记得加括号

实现

#include <bits/stdc++.h> 
using namespace std;
const int maxn = 1<<21;
const int p = 100000000;
int n,m;
int G[13][13];
int F[13];
int dp[13][1<<13];
bool flag[1<<13];
int main()
{
	int t;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
    	for(int j=1;j<=m;j++)
    	{
    		scanf("%d",&G[i][j]);
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			F[i] = (F[i]<<1)+ G[i][j];
		}
	}
	for(int i=0;i<(1<<m);i++)
	{
		if(((i&(i<<1))==0) && ((i&(i>>1))==0))  flag[i] = 1;
	}
	dp[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<(1<<m);j++)
		{
			if(((j&F[i])==j) && flag[j])
			{
				for(int k=0;k<(1<<m);k++)
				{
					if((k&j)==0)
					{
						dp[i][j]+=dp[i-1][k];
						//cout<<"i : "<<i<<"j : "<<j<<endl; 
						//cout<<dp[i][j]<<endl;
					}
				}
			}
		}
	}
	int ans = 0;
	for(int i=0;i<(1<<m);i++)
	ans+=dp[n][i],ans%=p;
	cout<<ans;
    return 0;
}

P3092 [USACO13NOV]No Change G

思路

  • 优化的方式是前缀和+二分
  • f [ i ] f[i] f[i]表示选择第i种方式最多可以付多少件

实现

//来自洛谷题解区UltiMadow
#include<bits/stdc++.h>
#define MAXN 100010
using namespace std;
int n,k,ans;
int sum[MAXN],coin[MAXN];
int f[1<<17];
bool flag=0;
int find(int l,int r,int s)
{
	int now = sum[l],ret=l;
	while(l<=r)
	{
		int mid=l+r>>1;
		if(sum[mid]-now<=s)ret=mid,l=mid+1;
		else r=mid-1;
	}
	return ret;
}
int main()
{
	scanf("%d%d",&k,&n);
	for(int i=1;i<=k;i++)scanf("%d",&coin[i]);
	for(int i=1;i<=n;i++)scanf("%d",&sum[i]),sum[i]+=sum[i-1];
	int max_state=(1<<k)-1;
	for(int i=0;i<=max_state;i++)
	{
		for(int j=1;j<=k;j++)
		{
			if((i>>j-1)&1)continue;
			f[i|(1<<j-1)]=max(f[i|(1<<j-1)],find(f[i],n,coin[j]));
		}
		if(f[i]==n)
		{
			int a=i,cnt=0;flag=1;
			for(int j=1;j<=k;j++)
			{
				if((i>>j-1)&1)continue;
				cnt+=coin[j];   //算出剩下多少钱 
			}
			ans=max(ans,cnt);
		}
	}
	if(!flag)printf("-1");
	else printf("%d",ans);
	return 0;
}

欢迎指正

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值