状压深入练习

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

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 <iostream>
#include <bits/stdc++.h>
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<n; i++)
        {
            string st;
            cin>>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<<n) ; state ++) // 杀敌状态
        {
            if(dp[state] <= 0)
                continue;
            // 构造出 当前吕布状态
            int lvLevel = 1, nowExp = 0;

            for(int j=0; j<n; j++)
            {
                if(state & (1<<j))
                    nowExp += g[j].exp;
            }
            lvLevel = nowExp / 100;

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

            lvLevel++;
            // cout<<"state: "<<state <<"level: "<<lvLevel<<endl;
            for(int i=0; i<n; i++) //找出还没杀死的
            {
                if( (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<<"吕布出招次数: "<<timLv <<"  第 "<<i<<" 号怪兽出招: "<<timG<<endl;
                if(nowExp + g[i].exp >= lvLevel * 100)
                    dp[state|(1<<i)] = max(dp[state|(1<<i)], dp[state] - ( (timLv-1) * hurtG) + inhp);
                else
                    dp[state|(1<<i)] = max(dp[state|(1<<i)], dp[state] - ( (timLv-1) * hurtG));

            }
        }
        if(dp[(1<<n)-1] <= 0)
            printf("Poor LvBu,his period was gone.\n");
        else
            printf("%d\n",dp[(1<<n)-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 <memory.h>
#include <math.h>
#include <algorithm>
#include <bits/stdc++.h>

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;i<g_Width;i++)
    {
        if( ((1<<i) & state ) > 0) num++;
        else
        {
             if(num%2)
                return false;
             num = 0;
        }
    }
    if(num%2)
        return false;
   // cout<<state<<endl;
    return true;
}

int FitLast(int stateA,int stateB)
{
    int i=0;
    while(i<g_Width)
    {
        if((stateA&(1<<i)) == 0)
        {
            if((stateB&(1<<i)) == 0)
                return false;
            i++;
        }
        else
        {
            if((stateB&(1<<i)) == 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<<g_Width); state++)
        {
            if(FitFirstLine(state))
                dp[0][state] = 1; // 方案为1
        }
        for(int i=1;i<g_Height;i++)
        {
            for(int k=0; k<(1<<g_Width); k++) // 当前行的状态
            {
                for(int p=0; p<(1<<g_Width); p++) // 上一行的状态
                    if(FitLast(k,p))
                        dp[i][k] += dp[i-1][p];
            }
        }

        printf("%lld\n",dp[g_Height-1][(1<<g_Width)-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 <iostream>
#include <bits/stdc++.h>

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: "<<i<<"  dig: "<<t%3<<endl;
            t/=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; i<n; i++)
        {
            dp[tri[i]][i] = 0;
        }
        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; i<n; i++)
                    ans = min(ans,dp[state][i]);
        }

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

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37591656/article/details/81903651