状态压缩DP

简单题

1.学校食堂

https://vijos.org/p/1546

看起来很简单,结果写了好久好久。我太弱啦。

dp[i][j][k]表示i以前的都处理好了,j表示 i及i以后的七个人的01处理状态,k表示这个状态是由哪个人转移过来的(1~mm表示i之前的,mm+1以后表示i和i之后的人) 

一开始感觉很好转移,写起来就出现了很多问题,根本不知道如何预处理?

后来问了杯哥,发现自己智障地以为i的状态就该向i以后的状态转移,其实i是可以向自己转移的QAQ

两种转移,一是j未到达全1,向i自己转移,二是j的前b项达到全1就向i+b转移。

自己写丑了,感觉别人的代码都贼短,不过懒得又抄一遍题解啦,反正自己瞎YY的丑就丑吧

//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int M=1000+5;
const int N=(1<<9);
int T,n,nn,mm,ti[M],rn[M],dp[M][N][17],no[M][N],cnt[N];
void init(){
    scanf("%d",&n);
    mm=0;
    for(int i=1;i<=n;i++){
    scanf("%d%d",&ti[i],&rn[i]);
    mm=max(mm,rn[i]);
    } 
    mm++;
}
void pre(){
    nn=(1<<mm)-1;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=nn;j++){
            int rr=min(i+mm-1,n),cc=0,xx;
            for(int k=0;k<mm;k++){
                if(j&(1<<k)){
                    if(i+k>rr) no[i][j]=1;
                    cc++;
                    xx=k+1;
                }
                else rr=min(rr,i+k+rn[i+k]);
            }
            cnt[j]=cc;
            if(cnt[j]==1) 
              dp[1][j][xx+mm]=0;
        }
}
void work(){
    for(int i=1;i<=n;i++) {
        for(int k=0;k<=mm;k++) {
            for(int j=0;j<=nn;j++) {
            if(no[i][j]||cnt[j]!=k) continue;
                for(int l=1;l<=(mm+1)*2;l++) {
                if(dp[i][j][l]==-1) continue;
                    for(int s=0;s<mm;s++) {
                        if(!(j&(1<<s))) {
                            int now=j|(1<<s);
                            if(no[i][now]) continue;
                            int pr=(l<mm+1)?i-(mm+1-l):i+l-(mm+1);
                            int cost=(ti[pr]|ti[i+s])-(ti[pr]&ti[i+s]);
                            if(dp[i][now][mm+1+s]==-1||dp[i][now][mm+1+s]>dp[i][j][l]+cost)
                                dp[i][now][mm+1+s]=dp[i][j][l]+cost;
                        
                        }
                    }
                    for(int s=0;s<mm;s++) {
                        if(!(j&(1<<s))) break;
                        int now=j>>s+1;
                        if(dp[i+s+1][now][l-s-1]==-1||dp[i+s+1][now][l-s-1]>dp[i][j][l])
                        dp[i+s+1][now][l-s-1]=dp[i][j][l];
                    }
                }
            }
        }      
    }
    int ans=1000000000;
    for(int i=1;i<=(mm+1)*2;i++){
        if(dp[n][1][i]!=-1) ans=min(ans,dp[n][1][i]);
    }   
    printf("%d
",ans);
}
void clear(){
    memset(dp,-1,sizeof(dp));
    memset(no,0,sizeof(no));
}
int main()
{
    scanf("%d",&T);
    while(T--){
        clear();
        init();
        pre();
        work();
    }
    return 0;
}
学校食堂

 

2.炮兵布阵

https://vijos.org/p/1424

先预处理出所有不合法的状态,有冲突的和每一行是1的位置为山地的为不合法。

dp[i][j][k]表示处理到了第i行,它的状态为j,它上一行的状态为k的最优解,转移的时候再判断一下三行直接冲不冲突即可。

//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=(1<<10);
int ans,nn,dp[105][61][61],a[105][20],n,m,zt[65],zts,gs[65],no[105][61];
char ch[20];
void pre(){
    nn=((1<<m)-1);
    for(int i=0;i<=nn;i++){
        int pre=-3,tp=i,k=0,v=0;
        while(tp){
            k++;
            if(tp&1) {
            v++;
            if(k-pre<3) break;
            pre=k; 
            }
            tp>>=1;
        }
        if(!tp) {
        zt[++zts]=i;
        gs[zts]=v;
        }
    }
    for(int i=1;i<=zts;i++) dp[1][1][i]=gs[i];
    for(int i=1;i<=zts;i++)
    for(int j=1;j<=m;j++)
    for(int k=1;k<=n;k++)
    if((zt[i]&(1<<(j-1)))&&a[k][j]) no[k][i]=1;
}
int check(int x,int y){
    for(int i=1;i<=m;i++) {
        if((zt[x]&(1<<(i-1)))&&((zt[y]&(1<<(i-1))))) return 0;
    }
    return 1;
}
int ok(int j,int k,int l){
    if(!check(j,k)) return 0;
    if(!check(k,l)) return 0;
    if(!check(j,l)) return 0;
    return 1;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%s",ch);
        for(int j=0;j<m;j++)
            if(ch[j]=='H')
                a[i][j+1]=1;    
    }
    pre();
    for(int i=2;i<=n;i++)
        for(int j=1;j<=zts;j++) {  //上二行 
            if((i==2&&j!=1)||(no[i-2][j])) continue;
            for(int k=1;k<=zts;k++) { //上一行
                if(no[i-1][k]) continue;
                for(int l=1;l<=zts;l++) {  //当前行 
                    if(no[i][l]||!ok(j,k,l)) continue;
                    dp[i][k][l]=max(dp[i][k][l],dp[i-1][j][k]+gs[l]);
                }
            }
        }
    for(int i=1;i<=zts;i++)
    for(int j=1;j<=zts;j++)
    ans=max(ans,dp[n][i][j]);
    printf("%d
",ans);
    return 0;
}
炮兵阵地

 

3.NOIP2016愤怒的小鸟

很久之前抄的题解,忘得差不多了,先码着

//Twenty
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
double eps=1e-10;
int t,n,m,f[20][20],cnt,dp[1<<20];
double x[20],y[20];
int min(int a,int b){
    return a<b?a:b;
} 
void makebag()
{
    memset(f,0,sizeof(f));
    for(int i=0;i<n;i++)
    for(int j=i+1;j<n;j++)
    {
        double x1=x[i],x2=x[j],y1=y[i],y2=y[j];
        double a,b;
        a=(y1*x2-y2*x1)/(x1*x2*(x1-x2));
        b=((y1*x2*x2-y2*x1*x1)/(x1*x2*x2-x1*x1*x2));
        
        if(a>=-eps) continue;
        for(int k=i;k<n;k++)
            {
                if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<=eps)
                  f[i][j]|=(1<<k);
           }    
       
    }
}
int dpp()
{
    memset(dp,127/3,sizeof(dp));
    dp[0]=0;int i;
    for(int v=0;v<(1<<n)-1;v++)
    {
        i=0;
        while(v&(1<<i)) i++;
        dp[v|(1<<i)]=min(dp[v|(1<<i)],dp[v]+1);
        for(int j=i;j<n;j++)
        dp[v|f[i][j]]=min(dp[v|f[i][j]],dp[v]+1);
    }
}

int main() 
{
  scanf("%d",&t);
  while(t--){
      scanf("%d%d",&n,&m);
      for(int i=0;i<n;i++)
      scanf("%lf%lf",&x[i],&y[i]);
      makebag();
      dpp();
      printf("%d
",dp[(1<<n)-1]);
  }
  return 0;
}
angry birds

 

4.坑人水题 bzoj3446 Cow Decathlon

     题目很水。不过第一次知道了预处理出状态中1的个数来优化转移。

     有个坑点是每个时间段可能有多个特别奖励的条件,调了好久。。

//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=1<<20;
int n,b,cntsp,sp[21][2],v[21][21],x,y,z,nn,dp[21][maxn],bit[maxn]; 
vector<int>s[22];
int main()
{
    scanf("%d%d",&n,&b);
    for(int i=1;i<=b;i++){
        scanf("%d%d%d",&x,&y,&z);
        sp[++cntsp][0]=y;
        sp[cntsp][1]=z;
        s[x].push_back(cntsp);
    }
    nn=(1<<n)-1;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    scanf("%d",&v[i][j]);
    
    for(int i=0;i<=nn;i++){
        int base=0;
        for(int j=1;j<=n;j++){
         if(i&(1<<(j-1))) base++;
        }
        bit[i]=base;
    }
    
    for(int i=1;i<=n;i++)
    for(int j=0;j<=nn;j++){
        if(bit[j]!=i) continue;
        for(int k=1;k<=n;k++)
         {
              if(j&((1<<(k-1))))
              {
                  int now=v[k][i];
                  for(int kd=0;kd<s[i].size();kd++){
                      int xx=sp[s[i][kd]][1],yy=sp[s[i][kd]][0];
                  if(now+dp[i-1][j^(1<<(k-1))]>=yy) 
                    now+=xx;
                  }//坑爹啊 一个点可能有多个特殊价值 
                  dp[i][j]=max(dp[i][j],dp[i-1][j^(1<<(k-1))]+now);
              }
         }
    }
    printf("%d
",dp[n][nn]);
    return 0;
}
Cow Decathlon

 

5.水题 扫雷

//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=10000+299;
int ans,n,a[maxn],dp[maxn][8],cnt[10];
void pre(){
    for(int i=0;i<8;i++)
        for(int j=0;j<3;j++)
            if(i&(1<<j)) cnt[i]++;
    dp[0][0]=dp[0][1]=1;
}
void init(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
}
void work(){
    for(int i=1;i<=n;i++) {
        for(int j=0;j<8;j++) {
            if(cnt[j]!=a[i]||(i==1&&(j&4))||(i==n&&(j&1))) continue;
            dp[i][j]+=(dp[i-1][j>>1]+dp[i-1][(j>>1)^4]);
            if(i==n) ans+=dp[i][j];
        }
    }
    printf("%d
",ans);
}
int main() 
{
    pre();
    init();
    work();
    return 0;
}
扫雷

 

6.vijos 兴奋剂检查

第一次见这样的题,又抄了题解,算是知道了状态压缩的含义,但对题目给的数据范围和这个压缩方式表示怀疑。

int has(int a,int b,int c,int d,int e){
    return a*(v[2]+1)*(v[3]+1)*(v[4]+1)*(v[5]+1)+b*(v[3]+1)*(v[4]+1)*(v[5]+1)+c*(v[4]+1)*(v[5]+1)+d*(v[5]+1)+e;                         
}
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=5000000+299;
int ans,n,m,dp[maxn],v[6],val,q[6];
int has(int a,int b,int c,int d,int e){
    return a*(v[2]+1)*(v[3]+1)*(v[4]+1)*(v[5]+1)+b*(v[3]+1)*(v[4]+1)*(v[5]+1)+c*(v[4]+1)*(v[5]+1)+d*(v[5]+1)+e;                         
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d",&v[i]);
    for(int i=1;i<=n;i++){
        scanf("%d",&val);
        for(int i=1;i<=m;i++) scanf("%d",&q[i]);
        for(int a=v[1];a>=q[1];a--)
            for(int b=v[2];b>=q[2];b--)
                for(int c=v[3];c>=q[3];c--)
                    for(int d=v[4];d>=q[4];d--)
                        for(int e=v[5];e>=q[5];e--){
                            int x=has(a,b,c,d,e),y=has(a-q[1],b-q[2],c-q[3],d-q[4],e-q[5]);
                            dp[x]=max(dp[x],dp[y]+val);
                            ans=max(ans,dp[x]);
                        }
    }
    printf("%d
",ans);
    return 0;
}
兴奋剂检查

 

7.水题 vijos 严厉的班长

一开始脑残了认为一个非1的数跟它自己互质,喵喵喵?

然后不是的话就很好做了,我们筛出100以内的质数,发现最大会有16个,因为大于59的没有选1划算。

知道了这一点就可以直接dp了

//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=1<<16;
int n,prime[18]={0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
int a[maxn],dp[101][maxn],zx[60],nn,ans=1e9+7;
void init(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
}
void pre(){
    for(int i=2;i<=58;i++) {
        int tp=i;
        for(int j=1;j<=16;j++) {
            if(tp==1) break;
            if(tp%prime[j]==0){
                zx[i]=zx[i]|(1<<(j-1));
                while(tp%prime[j]==0) tp/=prime[j];
            }
        }
    }
}
void work() {
    memset(dp,127,sizeof(dp));
    dp[0][0]=0;
    for(int i=0;i<n;i++)
        for(int j=1;j<=58;j++) 
            for(int k=0;k<maxn;k++) {
                if(k&zx[j]) continue;
                dp[i+1][k|zx[j]]=min(dp[i+1][k|zx[j]],dp[i][k]+abs(a[i+1]-j));
                if(i+1==n) ans=min(ans,dp[i+1][k|zx[j]]);
            }
    printf("%d
",ans);
}
int main()
{
    init();
    pre();
    work();
    return 0;
}
严厉的班长

 

8.水题 最小总代价

感觉没什么存在意义的水题。大概就是体现一下水是生命之源吧。

顺便vijos上的难度真是一点参考价值没有。

//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=1<<16;
int ans=1e9,n,nn,pr,v[20][20],cnt[maxn],dp[maxn][20];
void pre(){
   nn=(1<<n)-1;
   for(int i=1;i<=nn;i++){
          cnt[i]=0; pr=0; 
       for(int j=0;j<16;j++)
           if(i&(1<<j)) {cnt[i]++; pr=j+1;}
       if(cnt[i]==1) dp[i][pr]=0;
   }
}
int main()
{
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    memset(dp,127,sizeof(dp));
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    scanf("%d",&v[i][j]);
    pre();
    for(int i=1;i<=n;i++)
    for(int j=1;j<=nn;j++){
        if(cnt[j]!=i) continue;
        for(int k=1;k<=n;k++){
            if(dp[j][k]==-1) continue;
            for(int l=1;l<=n;l++){
                if(j&(1<<l-1)) continue;
                dp[j|(1<<l-1)][l]=min(dp[j|(1<<l-1)][l],dp[j][k]+v[k][l]);
            }
        }
    }
    for(int i=1;i<=n;i++)
    ans=min(ans,dp[nn][i]);
    printf("%d
",ans);
    return 0;
}
最小总代价

 

9.洛谷 2915 [USACO08NOV]奶牛混合起来Mixed Up Cows

水题

10.BZOJ 1087 [SCOI2005]互不侵犯King

水题

中等题

1.bzoj1294: [SCOI2009]围豆豆Bean

状压+计算几何

2.bzoj4572: [Scoi2016]围棋

轮廓线dp

 

3.自家测试 A题

奥妙重重的状压dp。

较难题

1.长沙集训 Adore

2.长沙集训 T4逛公园

可以模拟退火乱搞

原文地址:https://www.cnblogs.com/Achenchen/p/7474115.html