我是一只小小小小鸟

导航

 

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值是多少吗?

 

输入文件:

       输入文件只有一组数据,第一行为一个整数N1<=N<=20),表示树枝总数,以下N行每行有四个数abcw,分别为树枝的两个端点、颜色及权值,端点编号为012,……,N0号节点为根结点,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, 256M ,1s)

 在研究过Nim游戏及各种变种之后,Orez又发现了一种全新的取石子游戏,这个游戏是这样的:

n堆石子,将这n堆石子摆成一排。游戏由两个人进行,两人轮流操作,每次操作者都可以从最左或最右的一堆中取出若干颗石子,可以将那一堆全部取掉,但不能不取,不能操作的人就输了。

Orez问:对于任意给出一个初始一个局面,是否存在先手必胜策略。

输入数据

 输入数据存放在文本文件stone.in中。

文件的第一行为一个整数T,表示有 T组测试数据。对于每组测试数据,第一行为一个整数n,表示有n堆石子;第二行为n个整数ai,依次表示每堆石子的数目。

 输出数据

 输出数据存放在文本文件stone.out 中。

对于每组测试数据仅输出一个整数01。其中1表示有先手必胜策略,0表示没有。

样例输入

1

4

3 1 9 4

 

样例输出

0

 

数据范围

对于30%的数据  n5  ai105

对于100%的数据  T10 n1000 每堆的石子数目≤109

取石子游戏

   n堆石子依次为A1A2,……,An-1X,在A1,……,An-1都确定的情况下,存在且只存在一个自然数X,使得该局面为先手必败,所有别的局面都是先手必胜。设fl[i][j]表示对于XAi,……,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]由以下方式确定:

 

不妨设LR,当x<R时,fl[i][j]=x;(先手拿多少后手就拿多少,等先手拿完左边或者右边,后手就赢了)

 

x=R时,fl[i][j]=0(先手直接输);

 

RxL时,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;
}

  

posted on 2011-07-21 10:51  cloudygoose  阅读(1571)  评论(0)    收藏  举报