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[i−1][k][s−count[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[i−1][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[i−1][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;
}
欢迎指正