PKU 1678 I Love this Game
题目大意:
有n个数,两个小朋友从里面依次拿数,给你 个区间[a,b](0<a,b<=100),小朋友1拿完数a1之后,小朋友2拿的a2必须满足
a2-a1>=a && a2-a1<=b
同样小朋友2拿完数a2后,小朋友1再拿的a3也必须和a2符合这个条件,拿呀拿,拿到谁不能拿了游戏中止
n<=10000
a1必须在[a,b]这个区间内
小朋友1获得的分数是a1+a3+a5.....小朋友2获得a2+a4+a6.....
小朋友1希望他的分数减去小朋友2的分数的值尽量大(可能为负)
题解:
显然两个人拿的数是从小到大的,因此先排个序。从后往前做动态规划,f[i]表示当前拿数的小朋友(注意不是第一个小朋友)拿了a[i]后到游戏结束与另一个小朋友的最大差值。
f[i] = a[i] - max{f[j]} (a <= a[j] - a[i] <= b)
#include<cstring>
#include<stdio.h>
#include<iostream>
#define INT_MIN -2000000000
using namespace std;
int a[20000], n, ll, rr, f[20000];
void qsort(int l, int r)
{
int i = l, j = r, x = a[(l + r) / 2];
while (i < j)
{
while (a[i] < x) i++;
while (a[j] > x) j--;
if (i <= j)
{
int tmp = a[i]; a[i] = a[j]; a[j] = tmp;
i++; j--;
}
}
if (j > l) qsort(l, j);
if (i < r) qsort(i, r);
}
void gogo()
{
scanf("%d%d%d", &n, &ll, &rr);
for (int i = 1;i <= n;i++) scanf("%d", &a[i]);
qsort(1, n);
memset(f, 0, sizeof(f));
for (int i = n;i >= 1;i--)
{
int maxx = INT_MIN;
for (int j = i + 1;j <= n;j++)
{
int cz = a[j] - a[i];
if (cz > rr) break;
if (cz < ll) continue;
if (f[j] > maxx) maxx = f[j];
}
if (maxx == INT_MIN) maxx = 0;
f[i] = a[i] - maxx;
}
int ans = INT_MIN;
for (int i = 1;i <= n;i++)
if (a[i] >= ll && a[i] <= rr && f[i] >= ans) ans = f[i];
if (ans == INT_MIN) ans = 0;
printf("%d\n", ans);
}
int main()
{
freopen("1678.in", "r", stdin);
int opt;
scanf("%d", &opt);
while (opt > 0)
{
gogo();
opt--;
}
}
下篇东西转载自:
http://hi.baidu.com/%D0%A4%CC%AB%D2%AF%CB%E6%B1%E3/blog/item/6b73d2fb07d51d869e5146fa.html
硬币游戏
Description
Farmer John的奶牛喜欢玩硬币游戏,因此FJ发明了一种称为“Xoinc”的两人硬币游戏。
初始时,一个有N(5 <= N <= 2,000)枚硬币的堆栈放在地上,从堆顶数起的第I枚硬币的币值为C_i (1 <= C_i <= 100,000)。
开始玩游戏时,第一个玩家可以从堆顶拿走一枚或两枚硬币。如果第一个玩家只拿走堆顶的一枚硬币,那么第二个玩家可以拿走随后的一枚或两枚硬币。如果第一个玩家拿走两枚硬币,则第二个玩家可以拿走1,2,3,或4枚硬币。在每一轮中,当前的玩家至少拿走一枚硬币,至多拿走对手上一次所拿硬币数量的两倍。当没有硬币可拿时,游戏结束。
两个玩家都希望拿到最多钱数的硬币。请问,当游戏结束时,第一个玩家最多能拿多少钱呢?
Input
第1行:1个整数N
第2..N+1行:第i+1行包含1个整数C_i
Output
第1行:1个整数表示第1个玩家能拿走的最大钱数。
Sample Input
5
1
3
1
7
2
Sample Output
9
Hint
样例说明:第1个玩家先取走第1枚,第2个玩家取第2枚;第1个取走第3,4两枚,第2个玩家取走最后1枚。
Source
USACO NOV09 SILVER
***********************************************************************************
本题是求最优解,常用的方法有DP、贪心、搜索。
对N=2000的数据规模,搜索显然是会超时的。每步都取最优的贪心明显不对,样例就是反例。
根据题设条件,发现问题状态和两个因素有关:当前剩下的硬币数量和对手上一次拿走的硬币数量。对于当前状态,要得到最优解,需要枚举自己能拿走的硬币数量x。一旦这次自己拿走了x枚硬币(自然可以算出本次所拿的总钱数),就交由对手走,状态就转移成剩下total-x枚硬币(total是“上上”次取走硬币后,剩下的总硬币枚数),上一次拿走x枚硬币。对手也会拿走最多钱数的硬币。这里有个关键的地方要想通:在交给对方走之后,自己还能在以后的游戏中拿走多少钱数的硬币?答案是:剩下total-x枚的总钱数减去对手拿走的总钱数(这个值恰好是剩下total-x枚硬币,上一次拿走x枚硬币所表示的最优解)
令DP[c][p] 表示剩下c 枚硬币,上次对手拿走p枚硬币的最优解。SUM[i][j]表示从第i枚到第j枚硬币的钱数之和。
DP[c][p] = max{SUM[c-i+1][c] + (SUM[1][c-i] - DP[c-i][i])} 1 <=i <= min(2*p, c)
SUM[c-i+1][c]表示本次自己拿走第i枚的钱数和
SUM[1][c-i] - DP[c-i][i]表示在剩下的局面中自己还能拿的钱数和
上式化简得:DP[c][p] = max{SUM[1][c] - DP[c-i][i]}
边界状态:DP[0][*] = 0 (剩下0枚硬币,当然只能拿走0了)
目标状态:ans = DP[N][1]
可以先将SUM预处理出来。但是整个DP仍然有N^2个状态,每次转移是O(N),所以整个时间为O(N^3)。要通过全部数据,需要进一步优化才行。分析后发现,在按上述方程计算两个相邻状态DP[c][p] 和DP[c][p+1]时做了很多重复:
DP[c][p] = max{SUM[1][c] - DP[c-i][i]} 1 <=i<=min(2*p, c)
DP[c][p+1] = max{SUM[1][c] - DP[c-i][i]} 1 <=i<=min(2*(p+1), c)
= max{DP[c][p], SUM[1][c] - DP[c-(2*p+1)][2*p+1],SUM[1][c] - DP[c-(2*p+2)][2*p+2]}
这样就把转移时间降为O(1),整个时间降为O(N^2)了。当然,在处理2*p+1 > c 和 2*p+2 > c 要特别小心,否则就要造成数组访问出界的错误。
下一题来自广东省选2006
第二题:新红黑树。
问题描述:
A君和B君在玩一种叫做新红黑树的游戏,即在一棵由红枝和黑枝构成的树上轮流砍树枝,每次砍一枝,A君每次只砍红枝,B君每次只能砍黑枝。当其中某人已经没有树枝砍的时候,由另外一人砍,直到砍完全部树枝。树枝是带权的,每个人的总分是他砍的树枝的权值之和。那些由于其它树枝被砍掉而与根失去联系的树枝会自动消失,每次由A君先砍,设D=A君的得分-B君的得分,A君想让D最大,而B君想让D最小,A君和B君都是极其聪明的人,他们始终以最优策略进行整个游戏,你知道最后的D值是多少吗?
输入文件:
输入文件只有一组数据,第一行为一个整数N(1<=N<=20),表示树枝总数,以下N行每行有四个数a,b,c,w,分别为树枝的两个端点、颜色及权值,端点编号为0,1,2,……,N,0号节点为根结点,c=1表示红色,c=-1表示黑色,1<=w<=1000。
输出文件:
输出文件包含一个整数,为所求的D值。
输入样例:
3
0
1 1 100
1
2 -1 50
2
3 1 51
输出样例:
101
很容易看出是状态压缩动态规划,这题并不难,只是希望借这题看一下状态压缩+动归+博弈的体现。
f[k][0..1]表示还剩下状态k所表示的树枝时先手能获得的最大差值,0..1表示目前先手砍的是红树枝还是黑树枝
方程也很好写
f[k][now] = max{score[j] - f[k - tree[j]][1-now]} 其中tree[j]为j的子树所表示的状态(因为砍掉一根树枝就砍掉了它的子树)
实现形式可以参照树形动态规划的思考,使用记忆化搜索。
下一篇来自
http://blog.csdn.net/tiaotiaoyly/article/details/2700118
ZJU1607
题目描述:
游戏者AB分享N个面包圈,由A开始轮流取面包,每次不超过M个。当某人取得最后一个面包圈时为胜利者。胜利者可以吃掉已经取得的面包圈,失败者将自己取得的面包圈拿出来重新开始游戏,并由失败者开始取。假设AB两人都使用最佳决策,问A最多能吃到多少面包圈。
题目分析:
想了好久,DP思路还是不清晰,在网上搜了一下才豁然开朗。不过网上那个公式比较冗余,我把他的公式降了一维下来。
设f[s][d][r]表示当前游戏者取得s个,对手取得d个,剩余r个面包圈时,当前游戏者最终最多能吃到面包圈的数量。
当r>m时,f[s][d][r] = max{ s+d+r - f[d][s+k][r-k] (1<=k<=m) },当前游戏者不能一次将剩下的都取走;
当r<=m时,还可以考虑一下f[s][d][r] = s+d+r - f[0][0][d],当前游戏者可以取走剩下的面包圈获得胜利,开始新的一轮。
后记:
开始考虑的时候,列的方程太宏观了,似乎以前很多次都是这样。
有时候表示的状态不能有效地写出递推或递归方程的时候,一定要考虑增加状态表示的维度!
#include <stdio.h>
#include <cstring>
using namespace std;
#define N 105
#define clr(a) memset(a,0,sizeof(a))
int n,m;
int f[N][N][N] = {0};
//f[s][d][r]表示当前游戏者取得s个、对手取得d个,剩余r个时,最终能获得的最大数目。
int MAX(int a,int b){
return a>b?a:b;
}
int F(int s,int d,int r){
if(f[s][d][r]) return f[s][d][r];
if(r<=m){
if(s==0 && d==0) f[s][d][r] = r;
else f[s][d][r] = s+d+r-F(0,0,d);
}
int k;
for(k=1;k<=m;k++){
if (k >= r) break;
f[s][d][r] = MAX(f[s][d][r], s+d+r-F(d,s+k,r-k));
}
return f[s][d][r];
}
int main()
{
int i,j,k;
while(scanf("%d%d",&n,&m)!=EOF){
clr(f);
printf("%d\n",F(0,0,n));
}
return 0;
}
三、取石子游戏(stone.pas/.c/.cpp,
有n堆石子,将这n堆石子摆成一排。游戏由两个人进行,两人轮流操作,每次操作者都可以从最左或最右的一堆中取出若干颗石子,可以将那一堆全部取掉,但不能不取,不能操作的人就输了。
Orez问:对于任意给出一个初始一个局面,是否存在先手必胜策略。
输入数据
文件的第一行为一个整数T,表示有 T组测试数据。对于每组测试数据,第一行为一个整数n,表示有n堆石子;第二行为n个整数ai,依次表示每堆石子的数目。
对于每组测试数据仅输出一个整数0或1。其中1表示有先手必胜策略,0表示没有。
样例输入
1
4
3 1 9 4
样例输出
0
数据范围
对于30%的数据 n≤5 ai≤105
对于100%的数据 T≤10 n≤1000 每堆的石子数目≤109
取石子游戏
设n堆石子依次为A1,A2,……,An-1,X,在A1,……,An-1都确定的情况下,存在且只存在一个自然数X,使得该局面为先手必败,所有别的局面都是先手必胜。设fl[i][j]表示对于X,Ai,……,Aj,其中除X以外都确定的情况下,X为多少使得局面为先手必败,同理设fr[i][j]。则我们可以发现,fl[i][j]的确定仅与fl[i][j-1]、fr[i][j-1]以及Aj有关。设L=fl[i][j-1],R=fr[i][j-1],x=Aj,注意x>0,则fl[i][j]由以下方式确定:
不妨设L≥R,当x<R时,fl[i][j]=x;(先手拿多少后手就拿多少,等先手拿完左边或者右边,后手就赢了)
当x=R时,fl[i][j]=0(先手直接输);
当R<x≤L时,fl[i][j]=x-1(我得保证你无法在第一回就取完右边导致我必败,后面策略:一直保持右边比左边大一,有一个问题:如果先手直接让右边拿成R,那么我们直接把左边取成0,如果先手让左边变成小于R的数,立刻把策略改成保持右边和左边持平,之后就和第一种情况一样了)
当x>L时,fl[i][j]=x(先手拿多少后手也拿多少,如果L==R,那么比较简单,一种难处理的情况是L>R,这时,如果先手把右边取成了L,后手果断把左边取成R,然后就可以回到“先手拿多少后手也拿多少”这个节奏中去了)。
同理可确定fr[i][j]。这样,我们只需要通过一个O(n2)的动态规划就可以计算除fr[1][n-1],再判断它是否等于An即可。
#include<stdio.h>
const char inf[]="stone.in";
const char ouf[]="stone.out";
const char *ansstr[]={"0","1"};
const long maxn=1000;
long T,n;
long a[maxn+1];
long fl[maxn+1][maxn+1],fr[maxn+1][maxn+1];
long ans;
void in(){
long i;
scanf("%ld",&n);
for(i=1;i<=n;i++)scanf("%ld",a+i);
}
long get(long home,long away,long x){
if(x==away)return 0;
if(((x<home) && (x<away)) || ((x>home) && (x>away)))return x;
if(home<away)return x+1;
return x-1;
}
void work(){
long i,j,k,l;
for(i=1;i<=n;i++)fl[i][i]=fr[i][i]=a[i];
for(l=1;l<n;l++){
for(i=1;i+l<=n;i++){
j=i+l;
fl[i][j]=get(fl[i][j-1],fr[i][j-1],a[j]);
fr[i][j]=get(fr[i+1][j],fl[i+1][j],a[i]);
}
}
if(n==1)ans=1;
else ans=1-(fr[1][n-1]==a[n]);
}
void out(){
printf("%s\n",ansstr[ans]);
}
main(){
freopen(inf,"r",stdin);
freopen(ouf,"w",stdout);
for(scanf("%ld",&T);T;T--){
in();
work();
out();
}
return 0;
}
浙公网安备 33010602011771号