今天打了一场CERC 2014的题目,确实感觉不一样,有点lrj书上的题目的感觉,灵活、看似简单实则难解、算法隐藏较深。 场上只出了4题,都是水题。
赛后发现很多DP,该题就是一道博弈DP。 怪物想让我得到的最少,我想得到的多,两者都选择最优策略,是不是很眼熟? 其实方法很像紫书P279 ”最大面积最小的三角剖分“ 。
像背包问题,我们按照一定的阶段来递推,那么对于当前物品i ,我又两种决策:取还是不取 。而对于怪物,也有两种决策:用魔法还是不用(前提是还能发动),上面说了,两者都用最优策略,所以状态方程就很显然了:if(j>0) d[i][j] = max(d[i+1][j],min(a[i].v-a[i].c,d[i+1][j-1]-a[i].c));
问题是提交后WA,实在想不出bug,搜了唯一一篇题解上说:通过反证法可以发现,最优序列一定是价值递增的。
后来经过高神的讲解,大致明白了为什么要先买价值低的。因为怪物不知道未买商品的价值,而我自己只能获得一件商品的价值,那么肯定要将价值高的保留到后面买。
对于相邻两件物品,到底先买哪一件,我们可以做一个假设,每一个假设都有两种情况:价格的高低分类。 这样,我们可以得出结论,其实无论什么情况都应该先买价值低的(请自己试着证明),当然,这是在怪物一定释放魔法的情况下 。而怪物要是不放魔法呢? 其实在DP的过程中已经选取最优方案了(分成了怪物是否释放魔法和我是否买一件商品),所以我可以向后买价值高的。 这样,就完整的解释了为什么要事先按照价值进行排序了。
细节参见代码:
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<vector> #include<map> #include<list> #include<cmath> #include<set> #include<queue> using namespace std; typedef long long ll; const int INF = 100000000; const int maxn = 200000; int T,n,m,kase = 0,k,d[maxn][20],vis[maxn][20]; struct node{ int v,c; bool operator < (const node& rhs) const { return v < rhs.v; } }a[maxn]; int dp(int i,int j) { int& ans = d[i][j]; if(i == n+1) return ans = 0; if(vis[i][j] == kase) return ans; vis[i][j] = kase; ans = -INF; int v; if(j > 0) v = min(a[i].v-a[i].c,dp(i+1,j-1)-a[i].c); else if(j == 0) v = a[i].v-a[i].c; ans = max(dp(i+1,j),v); return ans; } int main() { scanf("%d",&T); while(T--) { scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) { scanf("%d%d",&a[i].v,&a[i].c); } ++kase; sort(a+1,a+n+1); int ans = dp(1,k); printf("%d\n",ans); } return 0; }