状压dp 练习

参考雨巨视频:https://www.bilibili.com/video/BV1z441137v6?p=1

1.互不侵犯

  题目传送门

  参考博客:https://www.cnblogs.com/ljy-endl/p/11627018.html     //tql

 

 

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,K,m;
ll f[17][155][155],ans=0;
int num[155],s[155];
int s0=0;
// f[i][j][k]表示第i行 ,状态为 j ,已经用了 k 个国王所产生的方案数 
void pre(){//预处理 
    int i,j,k; 
    memset(f,0,sizeof(f));
    for(int i=0;i<m;++i){
        if(i&(i<<1)) continue;    //两个国王相邻(会打起来 例: 1001101 
        k=0;
        for(int j=0;j<n;++j) if(i&(1<<j)) k++; //记录方案有几个国王 
        s[++s0]=i;    //预处理有效的方案 
        num[s0]=k;    //保存每一种方案可放国王数量 
    }
}
void dp(){
    f[0][1][0]=1;    //第0行放0个国王的方案数为1 
    for(int i=1;i<=n;++i){    //枚举行 
        for(int j=1;j<=s0;++j){    //枚举可行状态 
            for(int k=0;k<=K;++k){    //枚举国王数量 
                if(k<num[j]) continue;    
                for(int t=1;t<=s0;++t){    //枚举上一行的状态 
                    // s[t]&s[j] :s[t]至少有一个在国王在s[j]上面 例:s[t]:  100010001 s[j]: 001010001
                    // s[t]&(s[j]<<1): s[t]至少有一个在国王在s[j]右上方 例:s[t]:  100010001 s[j]: 100100001
                    // s[t]&(s[j]<<1): s[t]至少有一个在国王在s[j]左上方 例:s[t]:  100010001 s[j]: 100001001
                    if((!(s[t]&s[j]))&&(!(s[t]&(s[j]<<1)))&&(!(s[t]&(s[j]>>1))))
                        f[i][j][k]+=f[i-1][t][k-num[j]];
                        //不冲突,加上 上一行 t状态下的[k-num[j]]个国王的方案数 
                }
            }
        }
    }
    for(int i=1;i<=s0;++i) ans+=f[n][i][K];    //累加统计 
    cout<<ans<<"
";
}
int main(){
    cin>>n>>K;
    m=(1<<n);  
    pre();
    dp();
    
    return 0;
}

2.No Change

  题目传送门

      参考博客:https://ac.nowcoder.com/discuss/633882

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int qs=1e5+7;
const int inf =0x3f3f3f3f;
int n,m,s[qs],sum,con[20],f[qs];
//f[i]表示 状态i下可以买到的物品的数量 
int Find(int x,int k){//二分找到可以买到的最大价值的下标 
    int l=x,r=m,ans=x;
    while(l<=r){
        int mid=((l+r)>>1);
        if(s[mid]-s[x]<=k) ans=mid,l=mid+1;
        else r=mid-1;
    }
    return ans;
}

int cal(int x){    //状态x下每个1的位置代表用掉的硬币下标 
    int p=sum;
    for(int i=0;i<n;++i){
        if((x>>i)&1) p-=con[i];
    }
    return p;
}

int main(){
    cin>>n>>m;
    sum=0;
    for(int i=0;i<n;++i) cin>>con[i],sum+=con[i];
    //统计前缀和,用来二分取区间 
    for(int i=1;i<=m;++i) cin>>s[i],s[i]+=s[i-1]; 
    for(int i=0;i<(1<<n);++i) f[i]=-inf; f[0]=0;
    for(int i=0;i<(1<<n);++i){ //枚举(1<<n)种状态 
        for(int j=0;j<n;++j){    //枚举n枚硬币 
            //在i状态下没有使用j硬币 
            if(!((i>>j)&1)) continue;
            if(f[i-(1<<j)]>=0)
            //找到过去状态,计算购买东西数量 
                f[i]=max(f[i],Find(f[i-(1<<j)],con[j]));
        }
    }
    int ans=-1;
    for(int i=0;i<(1<<n);++i){
        if(f[i]==m) ans=max(ans,cal(i));
    }
    cout<<ans<<'
';
    return 0;
}

//理解了一整天 划水了一整天

//雨巨讲的太好了,我竟然也能理解QAQ

原文地址:https://www.cnblogs.com/Suki-Sugar/p/14635106.html