這個題數據有點水,有個奇葩程序可以過。。。(也不知道爲什麼。。。)
/*************************************************************************
> Author: wzw-cnyali
> Created Time: 2017/3/8 21:23:17
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)
#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)
#define debug(...) fprintf(stderr, __VA_ARGS__)
#define mem(a, b) memset((a), b, sizeof(a))
template<typename T> inline bool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }
int read()
{
int sum = 0, fg = 1; char c = getchar();
while(c < '0' || c > '9') { if (c == '-') fg = -1; c = getchar(); }
while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * fg;
}
const int Size = 100000;
const int inf = 0x3f3f3f3f;
int a[Size];
int main()
{
int Case = read();
while(Case--)
{
int n = read();
REP(i, 1, n)
{
a[i] = read();
}
if(abs(a[1] - a[n]) <= 1) printf("0\n");
else printf("1\n");
}
return 0;
}
可以無視這種AC代碼。
接下來來正經的談正解:
首先,講個性質,取石子時,當先手使得左右兩邊的石子相等時,可以保證先手必勝,因爲當先手取到左右石子相等的局面時,則後手無論在哪一堆取石子,接下來先手都可以有相同的方法取另一堆石子,這樣又回到了原來的狀態(左右石子相等),則無論如何先手都有辦法應對後手的取法。
設在區間[i, j]左邊放left[i][j](可以爲0)個石子後(區間內石子保持原狀),產生的局面爲先手必敗局面(這是重點),right[i][j]就是在右邊放(定義類似);設a[i]爲第i堆石子個數。
最後我們只用看 left[2][n] 是否等於 a[1] 就可以了。(如果等於就說明這個情況先手必敗,否則先手必勝)
這一題可以注意到對於一段區間[L, R], 若L+1到R的石子數固定, 那麼使得在這段區間上先手必敗的a[L]有且僅有一個。
接下來證明left[i][j]與(right[i][j]同理)是唯一的:
1)若存在兩或以上個left[i][j],則從某一個必定可一步轉移到另一個,矛盾,故最多隻有一個;
2)若沒有left[i][j],那麼必勝態只可能通過拿右邊的石子轉移到某個必敗態,由於此時左邊石子可以是任意個,右邊石子數固定,於是必有右邊的某一個石子數對應了多種必敗態,這些必敗態只有左邊的石子數不同,與1矛盾。
最後我們考慮left[i][j](同right[i][j])的求法,大力分類討論:
設 L = left[i][j - 1] , R = right[i][j - 1] , X = a[j] . 通過下面的分析我們可以發現left[i][j]只和L, R, X三個數有關.
1)邊界條件:left[i][i] = right[i][i] = a[i],兩堆一樣的,後手照着先手拿的數量在另一堆拿即可。
2)首先, 最容易想到的是, R = X的情況, 這時[i, j]這段區間已經先手必敗, 那麼left[i][j] = 0。
3)若 x < L && x < R, left[i][j] = x, 因爲此時當兩堆石子相同時,後手照着先手取,這樣先手一定會先拿完某一堆,後手佔據主動,這之後又會出現某一種我們分類討論中的情況,相當於一種遞歸,直到後手獲勝。
4)若 x < L && x > R, left[i][j] = x - 1。此時如果先手在左邊拿使其個數變爲y,若y < R,則後手在右邊也拿到y即可變爲情況3;若y >= R,則後手在右邊拿到y + 1, 於是回到了相同的情況。如果先手在右邊拿到y,若y < R,同樣可到情況3;若y = R, 直接把左邊的堆拿完,先手便面臨敗局;若y > R,則在左邊拿到y - 1,回到相同情況。
5)若 x < R && x >= L, left[i][j] = x + 1, 與情況4恰好相反。
6)若 x > L && x > R,left[i][j] = x。若先手將某一堆拿到了y,且y > R, 跟着先手拿即可回到相同情況;若y < L,回到情況3;若在在左邊拿到L或在右邊拿到R,後手拿掉另一堆即可;否則根據情況在另一堆拿到y - 1或是y + 1,分別對應情況4和5。
right[i][j]的求法與left[i][j]完全對稱,最後只需判斷 a[1] == left[2][n]。
代碼如下C++:
/*************************************************************************
> Author: wzw-cnyali
> Created Time: 2017/3/9 20:37:10
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)
#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)
#define debug(...) fprintf(stderr, __VA_ARGS__)
#define mem(a, b) memset((a), b, sizeof(a))
template<typename T> inline bool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }
int read()
{
int sum = 0, fg = 1; char c = getchar();
while(c < '0' || c > '9') { if (c == '-') fg = -1; c = getchar(); }
while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * fg;
}
const int Size = 1010;
const int inf = 0x3f3f3f3f;
int a[Size];
int Left[Size][Size], Right[Size][Size];
int main()
{
#ifndef ONLINE_JUDGE
freopen("input.in", "r", stdin);
freopen("output.out", "w", stdout);
#endif
int Case = read();
while(Case--)
{
int n = read();
REP(i, 1, n)
{
Left[i][i] = Right[i][i] = a[i] = read();
}
REP(k, 1, n - 1)
{
REP(i, 1, n - k)
{
int j = i + k, L, R, z;
L = Left[i][j - 1], R = Right[i][j - 1], z = a[j];
if(z == R) Left[i][j] = 0;
else if((z < L && z < R) || (z > L && z > R)) Left[i][j] = z;
else if(z >= L && z < R) Left[i][j] = z + 1;
else Left[i][j] = z - 1;
L = Left[i + 1][j], R = Right[i + 1][j], z = a[i];
if(z == L) Right[i][j] = 0;
else if((z < L && z < R) || (z > L && z > R)) Right[i][j] = z;
else if(z >= R && z < L) Right[i][j] = z + 1;
else Right[i][j] = z - 1;
}
}
printf("%d\n", a[1] == Left[2][n] ? 0 : 1);
}
return 0;
}