状压dp
这个真的不会,连状压这种思想都不太会……
-----------------------------------------------------------------------------------------------------------
简单来说,状压就是把一段需要进行决策的状态用二进制0/1的方式变成一个数。
一般适用于数据规模小、状态多的情况。
进行状态转移就要用位运算了。
常规操作:
1.地图初始化
一般都是把每行的状态表示成一个数。
以炮兵阵地为例子:
for(int i=0;i<n;++i)
for(int j=0;j<m;++j){
cin>>c;
map[i]<<=1; //二进制左移一位
if(c=='H') map[i]+=1; //如果是H标记为1
}
2.dp状态设计
这个其实变化很多,一般都有 dp(i,j) 表示第i行状态为j时的最大方案数。
有时候需要滚动数组优化,这个也只能多做题了。
3.状态的转移
一般都是比较 原来的自己、前一个状态+增长,取较大值。
模板题,适合状压入门。
做法:枚举状态,转移。
嗯?dp这么简单?
其实感觉dp最难的不在于某一部分,在于状态设计、初始化、状态转移、统计答案这些步骤合起来的思维难度。
#include<iostream>
using namespace std;
#define N 114514
#define M 1919810
#define ll long long
ll n,m;
char c;
ll map[N]; //二进制表示地图初始状态,每行用一个数表示
ll sum[N]; //状态为i时有多少个1
ll dp[1<<10][1<<10][3]; //dp(i,j,k) 表示第k-1行状态为i,第k行状态为j
//滚动数组优化,因为状态只涉及三行,就直接mod 3
ll get(ll x){ //查找当前状态x中有多少个1
ll tot=0;
while(x){
if(x&1) ++tot; //x二进制的最后一位& 1
x>>=1; //右移看下一位
}
return tot;
}
int main(){
cin>>n>>m;
for(int i=0;i<n;++i)
for(int j=0;j<m;++j){
cin>>c;
map[i]<<=1; //二进制左移一位
if(c=='H') map[i]+=1; //如果是H标记为1
}
for(int i=0;i<1<<m;++i) //至多1<<m个状态
sum[i]=get(i);
for(int i=0;i<1<<m;++i)
if(!(i&map[0]||i&i<<1||i&i<<2)) //判非法条件初始化
dp[0][i][0]=sum[i]; //初始化dp数组
for(int i=0;i<1<<m;++i) //上一行状态
for(int j=0;j<1<<m;++j) //当前行 这里出错了,原来是i<<m和j<<m,牢记
if(!(i&j||i&map[0]||j&map[1]||i&i<<1||i&i<<2||j&j<<1||j&j<<1))
dp[i][j][1]=sum[i]+sum[j];
for(int i=2;i<n;++i) //正式dp过程
for(int j=0;j<1<<m;++j){ //上一行
if(j&map[i-1]||j&j<<1||j&j<<2) continue;
for(int k=0;k<1<<m;++k){ //当前行原来状态
if(k&j||k&map[i]||k&k<<1||k&k<<2) continue;
for(int l=0;l<1<<m;++l){ //上上行
if(l&j||l&k||l&map[i-2]||l&l<<1||l&l<<2) continue;
dp[j][k][i%3]=max(dp[j][k][i%3],dp[l][j][(i-1)%3]+sum[k]); //状态转移方程
}
}
}
ll ans=0;
for(int i=0;i<1<<m;++i)
for(int j=0;j<1<<m;++j)
ans=max(ans,dp[i][j][(n-1)%3]); //统计答案,取最后一行最大值
cout<<ans;
return 0;
}
//记得去括号
虽然我是真的不想写dp,不过还是要练啊啊啊啊啊啊啊啊啊啊啊啊啊!
看到M<=20,可以考虑对M状压,但怎么搞呢?
考虑将每个队伍所占有区间作为状态。
对于每一个乐队,一段区间中队员的站队情况便可看成一个状态。
可以预处理出每个乐队在完成排队后的区间长度(就是人数)。
怎么转移呢?
依次枚举乐队,把乐队的人右移到队列最后。
转移式呢,转移代价是什么呢?
提前处理了初始情况每个乐队在区间内出现人次的前缀和,那么转移代价就是 $num_j-(sum_{len,j}-sum_{len-sum_j,j})$
等一下,先放这里。
#include
using namespace std;
#define N 114514
#define M 1919810
#define ll long long
#define inf 1e9
ll n,m,a[N],l[N],sum[N][25];
ll f[1<<21];
int main(){
cin>>n>>m;
for ( int i=1;i<=n;++i){
cin>>a[i];
for ( int j=1;j<=m;++j) sum[i][j]=sum[i-1][j]; //一段区间内某个乐队的人数
l[a[i]]++,sum[i][a[i]]++;
}
memset (f,0x3f3f3f3f, sizeof (f));
f[0]=0;
for ( int i=1;i<(1<
挺有趣的,这个1MB真TM有趣,于是就要用滚动数组优化了。
頑張って

浙公网安备 33010602011771号