一.基本思想
动态规划中最难的一类题之一,随随便便拿出一题都是提高+省选-
其主要思想是二进制位运算。
举个例子:
一条街上有n个路灯,路灯只有亮与不亮两种状态。
如果去描述这n个路灯的状态呢???
用数组描述显然可以,但有没有其他的方法???
二进制下的一个整数 k !!!
例如:
n等于8,k=183,k=10110111(2)
那么整数 k 就可以表示第123568个路灯亮,其余路灯不亮
其他功能怎么办???
比如判第i个灯是否亮,关闭第i个灯,如何关闭多个灯之类的
位运算来啦!!!
这个表格仅供参考,直接看最代码里最常用的几条即可
基本功能表
功能 | 实例 | 位运算 |
---|---|---|
去掉最后一位 | (101101->10110) | x>>1 |
在最后加一个0 | (101101->1011010) | x<<1 |
在最后加一个1 | (101101->1011011) | (x<<1)+1 |
把最后一位变成1 | (101100->101101) | x or 1 |
把最后一位变成0 | (101101->101100) | (x or 1) - 1 |
最后一位取反 | (101101->101100) | x xor 1 |
把右数第i位变成1 | (101001->101101,k=3) | x or (1 << (i-1)) |
把右数第i位变成0 | (101101->101001,k=3) | x and not(1 << (i-1)) |
右数第i位取反 | (101001->101101,k=3) | x xor (1 >> (k-1)) |
取末三位 | (1101101->101) | x and 7 |
取末k位 | (1101101->1101,k=5) | x and (1 >>k-1) |
取右数第k位 | (1101101->1,k=4) | x >>(k-1) and 1 |
把末k位变成1 | (101001->101111,k=4) | x or (1 >>k-1) |
末k位取反 | (101001->100110,k=4) | x xor (1 >>k-1) |
把右边连续的1变成0 | (100101111->100100000) | x and (x+1) |
把右起第一个0变成1 | (100101111->100111111) | x or (x+1) |
把右边连续的0变成1 | (11011000->11011111) | x or (x-1) |
取右边连续的1 | (100101111->1111) | (x xor (x+1)) >>1 |
个人总结最常用
以下的第i位均为从右起第i位
#include<bits/stdc++.h>
using namespace std;
int main(){
if(k&(1<<(i-1)));//判断第i位是否为1
k=k|(1<<(i-1));//将第i位为变成1
k=k&(!(1<<(i-1)));//将第i位为变成0
if(i&j);//状态i与状态j有重复
if((i<<1)&j);//状态i与状态j存在右上左下重复,即i的第x位与j的第x+1位有重复
if(i&(j<<1));//状态i与状态j存在左上右下重复,即i的第x+1位与j的第x位有重复
if(j&(j<<1));//状态j自身存在连续1
if(j&(~a[i]));//状态j在不该存在1的地方存在了1
return 0;
}
二.开始刷题
f[i][j][s]表示考虑从第1行到第i行,第i行为j状态下,一共放了s个国王的方案总数。
设:j为第i行的状态,p为第i-1行的状态
状态转移:f[i][j][s]+=f[i-1][p][s-king[j]]
#include<bits/stdc++.h>
using namespace std;
long long n,k;
long long f[12][1000][100],king[1000];
//f[i][j][s]表示考虑从第1行到第i行,第i行为j状态下,一共放了s个国王的方案总数
void init() {
int tot=1<<n;
//预处理每种状态下国王共摆放的数量
for(int j=0; j<tot; j++) {
int t=j;
while(t) {
king[j]+=t%2;
t/=2;
}
}
//预处理第1行摆放的每种状态下的方案数
for(int j=0; j<tot; j++) {
if(j&(j<<1))continue;
f[1][j][king[j]]=1;
}
}
void solve() {
int tot=1<<n;
for(int i=2; i<=n; i++) {
for(int j=0; j<tot; j++) {
//枚举第i行的状态
if(j&(j<<1))continue;//j状态下无法满足第i行的国王分布情况
for(int p=0; p<tot; p++) {
//枚举第i-1行的状态
//状态j与状态p矛盾
if(j&(p<<1))continue;
if(j&(p>>1))continue;
if(j&p)continue;
//状态j与状态p不矛盾,继承方案
for(int s=k;s>=king[j];s--){
f[i][j][s]+=f[i-1][p][s-king[j]];
}
}
}
}
long long ans=0;
for(int j=0;j<tot;j++)ans+=f[n][j][k];
cout<<ans;
}
int main() {
cin>>n>>k;
init();
solve();
return 0;
}
f[i][j]表示处理到第i行,第i行种植状态为j的情况的方案总数
设:j为第i行的状态,p为第i-1行的状态
状态转移方程:f[i][j]+=f[i-1][p]
#include<bits/stdc++.h>
using namespace std;
long long n,m,mod=100000000;
long long f[15][10000],a[15];
void init(){
int tot=1<<m;
//预处理第1行种植的每种状态下的方案数
for(int j=0;j<tot;j++){
if(j&(j<<1))continue;//自己行相邻矛盾
if(j&(~a[1]))continue;//非法种植矛盾
f[1][j]=1;
}
}
void solve(){
for(int i=2;i<=n;i++){
for(int j=0;j<tot;j++){
//枚举第i行的状态
if(j&(j<<1))continue;//自己行相邻矛盾
if(j&(~a[i]))continue;//非法种植矛盾
for(int p=0;p<tot;p++){
//枚举第i-1行的状态
if(p&(p<<1))continue;//自己行相邻矛盾
if(p&(~a[i-1]))continue;//非法种植矛盾
if(j&p)continue;//上下行相邻矛盾
//状态j与状态p合法,转移
f[i][j]=(f[i][j]+f[i-1][p])%mod;
}
}
}
long long ans=0;
for(int j=0;j<tot;j++)ans=(ans+f[n][j])%mod;
cout<<ans;
}
int main(){
int x;
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>x;
a[i]=a[i]*2+x;//处理第i行的合法种植状态
}
}
solve();
return 0;
}
稍稍有点难度,难度在于要考虑上上行的状态,多一维维护就好了
设:j为第i行的状态,k为第i-1行的状态,p为第i-2行的状态
状态转移方程:f[i][j][k]=max(f[i][j][k],f[i-1][k][p]+sum+king[j])
显然MLE,滚动数组优化一下就好了,用%滚动,那么存储从0~n-1
#include<bits/stdc++.h>
using namespace std;
int n,m,king[1200],f[4][1200][1200],a[1200],ans;
//f[i][j][k]表示考虑到从第一行到第i行,第i行状态为j,第i-1行状态是k的情况下的摆放炮兵的总数量
void init() {
int tot=1<<m;
//预处理状态j所摆放的炮兵总数量
for(int j=0; j<tot; j++) {
int t=j;
while(t) {
king[j]+=t%2;
t/=2;
}
}
//预处理第1(2)行摆放的每种状态下的方案数
for(int j=0; j<tot; j++) {
if(j&(j<<1))continue;
if(j&(j<<2))continue;
if(j&(~a[0]))continue;
for(int k=0; k<tot; k++) {
if(k&(k<<1))continue;
if(k&(k<<2))continue;
if(k&(~a[1]))continue;
if(j&k)continue;
f[1][k][j]=king[j]+king[k];
}
}
}
void solve() {
int tot=1<<m;
for(int i=2; i<n; i++) {
for(int j=0; j<tot; j++) {
//枚举第i行状态
if(j&(j<<1))continue;
if(j&(j<<2))continue;
if(j&(~a[i]))continue;
for(int k=0; k<tot; k++) {
//枚举第i-1行的状态
if(k&(k<<1))continue;
if(k&(k<<2))continue;
if(k&(~a[i-1]))continue;
if(j&k)continue;
for(int p=0; p<tot; p++) {
//枚举第i-2行的状态
if(p&(p<<1))continue;
if(p&(p<<2))continue;
if(p&(~a[i-2]))continue;
if(k&p)continue;
if(j&p)continue;
//满足摆放条件,状态转移
f[i%3][j][k]=max(f[i%3][j][k],f[(i-1)%3][k][p]+king[j]);
}
}
}
}
for(int j=0; j<tot; j++) {
for(int k=0; k<tot; k++)ans=max(ans,f[(n-1)%3][j][k]);
}
cout<<ans;
}
int main() {
int x;
char c;
cin>>n>>m;
for(int i=0; i<n; i++) {
for(int j=1; j<=m; j++) {
cin>>c;
if(c=='P')x=1;
else x=0;
a[i]=a[i]*2+x;
}
}
init();
solve();
return 0;
}