状压dp

因为有道ac自动机+状压dp的题,故开此坑。

状压dp:状态压缩dp(元素数量通常不超过20),借助位运算将状态压缩。

空间复杂度:O(n*n)

上一行的状态为now,下一行的状态为prev,通过枚举上一行所有状态,来更新当前行、当前状态的最优解。

给定n*m矩阵,行列都不超过20,有些格子可选有些不可选,需选出最多格子使得格子之间不相邻(无公共边)

#include <iostream>
#include <string.h>
using namespace std;
const int MAX_N=20;
const int MAX_M=20;
int state[MAX_N+1];
int dp[MAX_N+1][ 1 <<MAX_M];
bool not_intersect(int now,int prev)
{
    return (now&prev)==0;
}
bool fit(int now,int flag)
{
    return(now|flag)==flag;
}
bool ok(int x)
{
    return(x&(x/2))==0;
}
int count(int now)
{
    int s=0;
    while(now)
    {
        s+=(now&1);
        now>>=1;
    }
    return s;
}
int main() 
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<m;j++)
        {
            int flag;
            cin>>flag;
            state[i]|=(1<<j)*flag;
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<(1<<m);j++)
        {
            if(!ok(j)||!fit(j,state[i]))
            {continue;}
            int cnt=count(j);
            for(int k=0;k<(1<<m);k++)
            {
                if(ok(k)&&fit(k,state[i-1])&&not_intersect(j,k))
                   {
                       dp[i][j]=max(dp[i][j],dp[i-1][k]+cnt);
                   }
            }
        }
    }
    
    int ans=0;
    for(int i=0;i<(1<<m);i++)
    {
        ans=max(ans,dp[n][i]);
    }
    cout<<ans<<endl;
    return 0;
}
View Code

长度不超过16的字符串S,如果一个子序列是回文串,即可以移除它,求最少经过多少步能移除整个串。

序列回文?1:min(子集a次数,a的互补子集b次数),时间复杂度O(3n+n*2n

枚举子集代码

for (int t = 1; t < (1 << n); t++) {  // 枚举当前状态
     dp[t] = IsPalindrome(t) ? 1 : inf;  // 判断当前状态是否是回文,如果是回文则步骤数为 1
    for(int i = t; i; i = (i - 1) & t) { // 枚举 t 的所有子集
        dp[t] = min(dp[t], dp[i] + dp[t ^ i]);  // 更新当前状态的解的最小值
    }
}
printf("%d
", dp[(1 << n) - 1]);  // 输出最终答案
View Code

完整代码

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
char s[20];
int n,dp[1<<17];
bool ispa(int now)
{
    string str="";
    for(int i=0;i<n;i++)
    {
        if((now&(1<<i))==(1<<i))
        {
            str+=s[i];
        }
    }
    string str2=str;
    reverse(str2.begin(),str2.end());
    return str2==str;
}

int main()
{
    scanf("%s",s);
    n=strlen(s);
    
    for(int t=1;t<(1<<n);t++)
    {
        dp[t]=ispa(t)?1:inf;
        for(int i=t;i;i=(i-1)&t)
        {
            dp[t]=min(dp[t],dp[i]+dp[t^i]);
        }
    }
    
    int ans=dp[(1<<n)-1];
    cout<<ans<<endl; 
    return 0;
}
View Code

↓一道差不多的题:https://www.luogu.org/problem/P2704

灌溉机器人,一个P只影响得到四个方向八个点,所以第r行用r-1行和r-2行来推,每一行状态推一下,同行不互斥的状态有60种,滚动数组

#include<bits/stdc++.h>
using namespace std;
const int maxn=100;
const int maxm=10;
int state[maxn+1],cnt,stateline[maxn+1],sum[maxn+1];
int dp[maxn+1][65][65];

bool fit(int now,int flag)
{
    return (now|flag)==flag;
}
bool ok(int x)
{
    return (x&(x/2))==0&&(x&(x/4))==0;
}
int count(int now)
{
    int s=0;
    while(now)
    {
        s+=(now&1);
        now>>=1;
    }
    return s;
}

int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<m;j++)
        {
            char flag;
            cin>>flag;
            int tmp=flag=='P'?1:0;
            state[i]|=(1<<j)*tmp;
        }
    }
    for(int i=0;i<(1<<m);i++)
    {
        if(ok(i))
        {
            cnt++;
            stateline[cnt]=i;
            sum[cnt]=count(i);
        }
    }

    for(int i=1;i<=cnt;i++)
    {
        if(fit(stateline[i],state[1]))
        dp[1][1][i]=sum[i];
    }

    for(int r=2;r<=n;r++)
    {
        for(int i=1;i<=cnt;i++)
        {
            if(!fit(stateline[i],state[r]))continue;
            for(int j=1;j<=cnt;j++)
            {
                if(stateline[i]&stateline[j])continue;
                for(int k=1;k<=cnt;k++)
                {
                    if(stateline[i]&stateline[k])continue;
                    dp[r][j][i]=max(dp[r][j][i],dp[r-1][k][j]+sum[i]);
                }
            }
        }
    }
    int ans=0;
    for(int i=1;i<=cnt;i++)
    {
        for(int j=1;j<=cnt;j++)
        {
            ans=max(ans,dp[n][i][j]);
        }
    }
    cout<<ans<<endl;
    return 0;
}
View Code

n辆小车(物资)用一辆船运,每次运的车总重不能超过wei,每辆车重w[i],对船损坏值s[i],每次运输对船的损坏值为max 船上所有车损坏值,问运完所有车的min总共损坏值,枚举子集

#include<bits/stdc++.h>
using namespace std;
const int maxn=400;
const int maxm=16+1;
const int inf=0x3f3f3f3f;
int s[maxm+1],w[maxm+1];
int dp[1<<maxm];
int wei,n;

bool ok(int now)
{
    int ans=0;
    for(int i=0;i<n;i++)
    {
        if((now&(1<<i))==(1<<i))
        {
            ans+=w[i+1];
            if(ans>wei)
            return false;
        }
    }
    return true;
}

int count(int now)
{
    int ans=0;
    for(int i=0;i<n;i++)
    {
        if((now&(1<<i))==(1<<i))
        ans=max(ans,s[i+1]);
    }
    return ans;
}


int main()
{
    scanf("%d%d",&wei,&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&s[i],&w[i]);
    }

    for(int t=1;t<(1<<n);t++)
    {
        if(ok(t)){dp[t]=count(t);continue;}
        else dp[t]=inf;
        for(int i=t;i;i=(i-1)&t)
        {
            dp[t]=min(dp[t],dp[i]+dp[t^i]);
        }
    }
    
    int ans=dp[(1<<n)-1];
    cout<<ans<<endl;

    return 0;
}
View Code

n个点,有2n-1种状态(1到(1<<n)-1),给点填色,有线相连的点不能填同一个颜色,si表示状态i填色需要的最少颜色种类,求。枚举的时候枚举一下是否能用一个颜色就可以了。

如果mod=2^32,可以定义unsigned int,使其自然溢出。

#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
const int inf=0x3f3f3f3f;
int n,dp[1<<16];
char ch[20][20];
unsigned int mod=1ll<<32; 
bool isone(int now)
{
    bool ok[20]={false}; 
    for(int i=0;i<n;i++)
    {
        if(now&(1<<i))
        {
            ok[i+1]=true;
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(ch[i][j]=='1'&&ok[i]&&ok[j])
            return false;
        }
    }
    return true;
}


int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            cin>>ch[i][j];
        }
    }
    
    unsigned int ans=0,c=233;
    for(int t=1;t<(1<<n);t++,c*=233)
    {
        if(isone(t))dp[t]=1;
        else 
        {
            dp[t]=inf;
            for(int i=t;i;i=(i-1)&t)
            {
                dp[t]=min(dp[t],dp[i]+dp[t^i]);
            }
        }
        ans+=dp[t]*c;
    }

    cout<<ans<<endl;
    return 0;
}
View Code

暂时完结...

原文地址:https://www.cnblogs.com/myrtle/p/11441144.html