状压DP深入练习

归纳总结一下吧,不然学了就忘
目前接触过四种题型,

1、问图的布置方案数(母牛,铺砖,摆放棋子)

dp[ i ][ state ] = sigma( dp[i-1] [ stateB ] )

2、问图中元素的最大拜访个数(炮兵阵地)

dp[i] [state] = max( dp[i-1] [stateB ]) 遍历i-1的stateB

3、操作一系列事件得到的最值

state 可以推出 nstate
dp[ nstate ] = max( dp[nstate] , dp[state] + 代价) , 从当前state可以逐个分析已经获得的信息。

4、图的哈密顿路径(从起点出发,每个点恰好路过一次,限定一些路径,求最小代价)

dp[ state ] [ u ] 表示已完成的图的状态,已经当前点在u上 的最小代价
倘若 u -> v
dp [ state | (1 < < v) ][ v ] = max ( dp [ state | (1 < < v) ] [v], dp[ state ] + mp[[u] [v] )

1、HDU God of War

题意:

属于套路3
给定吕布攻击力、防御力、生命,给定n个敌人的攻击力、防御力、生命值、可得经验。
升级可以增加攻击力、防御力、生命。
问选择杀敌顺序使得所有怪物全部杀掉,而且剩余的生命值最大

思路:

养成DP思维,我们有2^n 种杀敌顺序,利用state [0 ,1 < < n ) ,可以把所以的顺序表示出来,这个时候可能会有疑问,000110, 表示杀了第二个敌人和第三个敌人,可是先杀哪个是和结果相关的,比如经验涨的就不一样,怎么思考这点? 我们要知道 ,000110 ,是由 000100 和 000010 推得到的,因此DP会在两条路径里选择最优。因此 111111111 表示的就是杀完所有怪物的最优解。

还说一个,当前杀敌状态,是可以算出等级的,因此也可以算出升级完的状态。
再说一个,吕布砍敌人t次才能使敌人死, 那他自己要承受 t-1 敌人的攻击。

代码:

#include 
#include 
using namespace std;
typedef long long ll;

const int maxn = 25;
const int MOD = 998244353;
struct Guai
{
    int ati,def,hp,exp;

} g[maxn];
int dp[ 1 << 20 ];
int main()
{
    int ati,def,hp;
    int inati,indef,inhp;
    while(scanf("%d%d%d%d%d%d",&ati,&def,&hp,&inati,&indef,&inhp) != EOF)
    {
        memset(dp,0,sizeof(dp));
        int n;
        scanf("%d",&n);
        for(int i=0; i>st;
            scanf("%d%d%d%d",&g[i].ati,&g[i].def,&g[i].hp,&g[i].exp);
        }
        dp[0] = hp;
        for(int state = 0; state < (1<state ++) // 杀敌状态
        {
            if(dp[state] <= 0)
                continue;
            // 构造出 当前吕布状态
            int lvLevel = 1, nowExp = 0;

            for(int j=0; jif(state & (1<exp;
            }
            lvLevel = nowExp / 100;

            int ATI = ati + lvLevel * inati;
            int DEF = def + lvLevel * indef;

            lvLevel++;
            // cout<<"state: "<<state <<"level: "<for(int i=0; iif( (state & (1 << i) )> 0)
                    continue;//杀过了

                int hurtLv = max(1, ATI - g[i].def);
                int hurtG = max(1, g[i].ati -DEF);
                int timLv =ceil(g[i].hp*1.0 / hurtLv );

                int lose = (timLv-1) * hurtG;
                if(lose >= dp[state])
                    continue;

                //cout<<"吕布出招次数: "<"  第 "<" 号怪兽出招: "<if(nowExp + g[i].exp >= lvLevel * 100)
                    dp[state|(1<state|(1<state] - ( (timLv-1) * hurtG) + inhp);
                else
                    dp[state|(1<state|(1<state] - ( (timLv-1) * hurtG));

            }
        }
        if(dp[(1<1] <= 0)
            printf("Poor LvBu,his period was gone.\n");
        else
            printf("%d\n",dp[(1<1]);
    }

    return 0;
}

2、POJ 2411 铺砖

思路:

按套路走,本题属于第一类问图种类数。
本题难点在于,当前状态和上一个状态如何算兼容。
现在讨论 (i,j)这个位置,
0 表示 这个位置摆放竖砖
1 表示这个位置摆放横砖,或者是左边横转的残余,或者是i -1 行的竖砖的残余

不兼容的情况有以下几种

1) (i j) 为0 , 若 (i -1 , j) 为0 ,那么不兼容,因为i - 1为0 意味着拜访了竖砖。

2 (i, j ) 为1, 1 可以表示很多情况, 只有这一种会出现不符合,
当前位置作为横铺的起点, 但是这个位置处于最末尾,那么他不可能拥有 横铺的残余
( i , j + 1) 必须是1 , (i - 1, j + 1) 也必须是 1。
当遇到0时, 操作完 i += 1 , 遇到 1 时,如果作为上一个竖铺的残余,那么 i += 1, 否则 i+=2。
这样保证了我们讨论的不会是横铺的残余位置

预处理一下第一行的合法状态,从第二行开始DP , 最终答案dp[ n ] [ 1 < < ( m ) - 1] ,最后一行不能有出现竖铺的起点

#include 
#include 
#include 
#include 

using namespace std;


#define MAX_ROW 11
#define MAX_STATUS 2048
int g_Width, g_Height;

bool FitFirstLine(int state)
{
    int num = 0;
    for(int i=0;iif( ((1< 0) num++;
        else
        {
             if(num%2)
                return false;
             num = 0;
        }
    }
    if(num%2)
        return false;
   // cout<
    return true;
}

int FitLast(int stateA,int stateB)
{
    int i=0;
    while(iif((stateA&(1<0)
        {
            if((stateB&(1<0)
                return false;
            i++;
        }
        else
        {
            if((stateB&(1<0)
                i++;
            else
            {
                if(i == g_Width-1 || !( stateA&(1<<(i+1)) && stateB&(1<<(i+1))))
                    return false;
                i+=2;
            }
        }
    }
    return true;
}

long long dp[15][(1<<15)];
int main()
{
    while(scanf("%d%d", &g_Height, &g_Width) != EOF )
    {
        if(g_Height == 0 && g_Width == 0) break;
        if(g_Height < g_Width ) swap(g_Height,g_Width);

        memset(dp,0,sizeof(dp));
        for(int state = 0; state < (1<if(FitFirstLine(state))
                dp[0][state] = 1; // 方案为1
        }
        for(int i=1;ifor(int k=0; k<(1<// 当前行的状态
            {
                for(int p=0; p<(1<// 上一行的状态
                    if(FitLast(k,p))
                        dp[i][k] += dp[i-1][p];
            }
        }

        printf("%lld\n",dp[g_Height-1][(1<1]);

    }
    return 0;
}

3、hdu 3001 Travelling (TSP问题 )

这是第四类问题,旅行商
注意本题的要求,每个点至多去两次,意味着 1 2 2 1 。 2 2 2 2 ,这类无 0 的三进制数都是合法的。用三进制表示状态,我们思考一下相比二进制表示,哪些地方需要变动
假设状态是state
1、二进制下,我们探讨 第 i 个点,在当前状态下有无被访问过 (u -> v ,u必须出现,v必须为0),是用的 state&(1 << i) , 得到 1 即访问过,得到 0 为未访问。
三进制下,我们也要考虑当前状态state , 在节点u 上,经过了几次, 在节点v 上,经过了几次。
做法,因为state实际值是一个十进制,我们 把state / 3 得到的是 第 1 位的 次数, state / 9 得到 第二位的次数。 这里初始化处理 得到 dig[state][ i ] 数组表示状态state 在 i 位上的次数
2、二进制下,我们推下一个节点的状态,state | (1 < < v) , 这里从十进制去分析 ,假设v是第3位,那就是 state + pow(2,3) , 因此在三进制中, 我们也需要 state + pow(3,v)。

以上就是三进制下的状压,脑子笨没想出来,希望以后记牢

代码:
#include 
#include 

using namespace std;

const int maxn = 59050;
const int inf = 0x1f1f1f1f;

int dp[maxn][12];
int mp[12][12];
int dig[maxn][12];
int tri[] = {1,3,9,27,81,243,729,2187,6561,19683,59049 };

void init()
{
    for(int i=1; i<59050; i++)
    {
        // 每一个状态
        for(int t=i,k=0; k<12; k++)
        {
            dig[i][k] = t%3;
            //cout<<"state: "<"  dig: "<%3<3;
            if(t == 0)
                break;
        }
    }
}

int main()
{
    int n,m;
    init();
    while(scanf("%d%d",&n,&m)  == 2)
    {
        memset(dp,inf,sizeof(dp));
        memset(mp,inf,sizeof(mp));
        while(m--)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            a--;
            b--;
            mp[a][b] = mp[b][a] = min(c,mp[a][b]);
        }
        for(int i=0; i0;
        }
        int ans = inf;
        for(int state = 1; state < tri[n]; state++) // state 三进制
        {
            int f = 1;
            for(int i = 0; i < n; i++)
            {
                if(dig[state][i] == 0)
                    f  = 0;
                if(dp[state][i] == inf)
                    continue;
                for(int j=0; j < n ; j++)
                {
                    if(dig[state][j] >= 2 || i == j || mp[i][j] == inf)
                        continue;
                    int nstate = state + tri[j];
                    dp[nstate][j] = min(dp[nstate][j], dp[state][i] + mp[i][j]);
                }
            }
            if(f)
                for(int i=0; istate][i]);
        }

        if(ans >= inf)
            ans = -1;
        printf("%d\n",ans);
    }

    return 0;
}

4、百度之星选拔赛1001:调查问卷

没那么裸的状压DP

题意: 给你n条长度m的序列, 现在在m里面选择搭配任意的m子序列,问n条序列在某种搭配下,不相同的序列个数大于k 的搭配数。
思路:让state表示选中的子序列。很显然我们要遍历 (1 << m)次状态。我们用DP[ state ] [ i ]记录当前搭配状态下,前i行有几种不同的序列,那么递推方程就是 dp[ state ][ i ] = dp[ state ] [ i-1 ] + i - 1 + X ; 是什么鬼? X代表前i行中,规定了搭配方案后,得到的序列出现的次数。 加上 i - 1 再减去 i 行在state 下的状态出现的次数,就得到了前i行不同序列的个数。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

using namespace std;

const int maxn = 1050;
typedef long long ll;

int cur[maxn];
char st[maxn];
int dp[maxn][1<<11], vis[1<<11];

int main()
{
    int t;
    cin >> t;
    int cas = 1;
    while(t--)
    {
        int n,m,k;
        scanf("%d%d%d",&n,&m,&k);
        for(int i=1; i<=n; i++)
        {
            scanf("%s",st+1);
            cur[i] = 0;
            for(int j=1; j<=m; j++)
            {
                if(st[j] == 'A')
                    cur[i] += (1<<(m-j));
            }
        }
        printf("Case #%d: ",cas++);
        if(n*(n-1)/2 printf("0\n");
        else
        {
            memset(dp,0,sizeof(dp));
            int high = 1<int ans = 0;
            for(int state = 0; statememset(vis,0,sizeof(vis));
                for(int j=1; j<=n; j++)
                {
                    int andLast = state & cur[j];
                    dp[j][state] = dp[j-1][state] + (j-1) - vis[andLast];
                    vis[andLast]++;
                }
            }
            for(int state = 0; state < high ; state++)
                if(dp[n][state] >= k)
                    ans++;

            printf("%d\n",ans);
        }
    }
    return 0;
}

5、ZOJ3777B - Problem Arrangement

秒啊,题目开始越来越不裸了

题意:给出矩阵mp[ n ][ n ] , 如果mp[ i ][ j ] 选中,那么 i 行 j 列就不能选了 ,求选中的n 个点权值之和大于 k 的选法。
思路:分析题目,往常的dp都和当前行状态相关,则需要开一维保存行信息。 本题由于每行只选一个每列只选一个,那么state就能记录下当前状态已经存在几行。我们让dp[ state ][ val ] 列上的被选中的状态state , 得到的价值val。那么答案就是 dp[ (1 << n) - 1] [ m ] ,本题用刷表法, 为什么不用填表法呢? 填表法需要从上一个状态推到当前的状态,也就是说我们要知道上一行的状态 ,我们很难知道 state 对应在 i 行的 具体是哪一列 , 也就无法 在 已知当前行的state ,推出哪些state是属于上一行。 刷表法就比较容易推, 我们可以由state 定位到当前是第几行,然后再找出当前可能的val值 , 通过当前的state 和 val 就能推出下一个 state | (1 << j ) 、val + mp[ i ][ j ] 。说的有点乱,总之先思考一下刷表还是填表。

具体代码在队友那,这是网上的刷表

代码:
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

#define N 15
#define INF 0x3f3f3f3f
#define LL long long

int a[N][N],fac[N];
int cnt[4500];

int dp[2][4500][505];
int gcd(int a,int b){
    if (b==0) return a;
    else return gcd(b,a%b);
}

void init(){
    fac[0] = 1;
    for (int i=1;i1]*i;
    memset(cnt,0,sizeof(cnt));
    for (int i=0;i<(1<<12);i++){
        int u=i;
        while (u){
            if (u&1)
                cnt[i]++;
            u>>=1;
        }
    }
}

int main(){
    int T;
    scanf("%d",&T);
    init();
    while (T--){
        int n,m;
        scanf("%d%d",&n,&m);
        for (int i=0;ifor (int j=0;jscanf("%d",&a[i][j]);
        memset(dp,0,sizeof(dp));
        for (int i=0;i0][1<0][i],m)]=1;
        for (int i=1;iint now=i&1,last=(i-1)&1;
            for (int j=0;j<(1<if (cnt[j]!=i)
                    continue;
                for (int k=0;kif (j&(1<continue;
                    for (int l=0;l<=m;l++)
                        dp[now][j|(1<int fm=fac[n];
        int fz=dp[(n-1)&1][(1<1][m];
        if (fz==0){
            printf("No solution\n");
        }else{
            int g=gcd(fm,fz);
            printf("%d/%d\n",fm/g,fz/g);
        }
    }

    return 0;
}

你可能感兴趣的:(状态压缩)