归纳总结一下吧,不然学了就忘
目前接触过四种题型,
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;
}