ARC105 D.Let‘s Play Nim,E.Keep Graph Disconnected題解

Gary_2005發表於2020-10-20

ARC105 D.Let’s Play Nim,E.Keep Graph Disconnected題解

總結兩個非常好的博弈題!

D. Let’s Play Nim

相信大家都知道NIM遊戲的結論吧: 如果 ⊕ x i = 0 \oplus x_i=0 xi=0則後手必勝,否則先手必勝。

則問題轉變為:

輪流將一個包裡的東西放到盤子裡,問最後拿出包裡物品的人能否讓 ⊕ x i = 0 \oplus x_i=0 xi=0?

這個比較複雜,我們可以分情況討論。

  • 若每一個物品出現的次數為偶數,則後手必勝。這是顯然的,由於後手可以不斷復刻先手的操作。

  • 否則,我們考慮倒數第二個拿走的那個人,如果他在沒用的那兩個中選擇最大的,放入權值最大的盤子中,這個盤子裡的權值和最終一定 ≥ s u m 2 \geq \frac{sum}2 2sum 。這說明了什麼呢?我們不妨可以再次分類討論:

    • max ⁡ { w i } > s u m 2 \max\{w_i\}>\frac{sum}2 max{wi}>2sum,則其餘盤子異或和一定 < max ⁡ { w i } <\max\{w_i\} <max{wi},則最終的異或和 ≠ 0 \neq 0 =0 (Tips:異或可以看作不進位的加法! )

    • max ⁡ { w i } = s u m 2 \max\{w_i\}=\frac{sum}2 max{wi}=2sum ,假設其餘盤子裡的異或和 = s u m 2 =\frac{sum}2 =2sum,若這個滿足,最後放入的兩個數一定相等。這個歸納一下就會發現這正是case1。

    綜上所述:若每一個數出現的次數都為偶數,後手必勝;否則拿走倒數第二個的人必勝。

總結:這題只需要分類討論,耐心證明就可以快速做出來。結論還是比較優美的!

Code:

/*
{
######################
#       Author       #
#        Gary        #
#        2020        #
######################
*/
//#pragma GCC target ("avx2")
//#pragma GCC optimization ("O3")
//#pragma GCC optimization ("unroll-loops")
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define rb(a,b,c) for(int a=b;a<=c;++a)
#define rl(a,b,c) for(int a=b;a>=c;--a)
#define LL long long
#define IT iterator
#define PB push_back
#define II(a,b) make_pair(a,b)
#define FIR first
#define SEC second
#define FREO freopen("check.out","w",stdout)
#define rep(a,b) for(int a=0;a<b;++a)
#define SRAND mt19937 rng(chrono::steady_clock::now().time_since_epoch().count())
#define random(a) rng()%a
#define ALL(a) a.begin(),a.end()
#define POB pop_back
#define ff fflush(stdout)
#define fastio ios::sync_with_stdio(false)
#define check_min(a,b) a=min(a,b)
#define check_max(a,b) a=max(a,b)
using namespace std;
const int INF=0x3f3f3f3f;
typedef pair<int,int> mp;
/*}
*/
map<int,int> app;
int N;
void solve(){
	app.clear();
	scanf("%d",&N);
	rb(i,1,N)
	{
		int ai;
		scanf("%d",&ai);
		app[ai]++;
	}	
	bool ok=true;
	for(auto ite=app.begin();ite!=app.end();ite++){
		ok&=!((ite->SEC)&1);
	}	
	ok^=1;
	ok^=(N&1);
	cout<<(ok? "First":"Second")<<endl;
}
int main(){
	int t;
	cin>>t;
	while(t--){
		solve();
	} 
	return 0;
}
/** 程式框架:
  *
  *
  *
  *
  **/

E. Keep Graph Disconnected

考驗思維的題目!(做了一晚上總算自己獨自ac了)

首先若只剩下兩個聯通快(1所在的和n所在的),最終誰會勝利呢?

很明顯,和能夠加的邊數有關。設1所在的聯通塊能加入的邊數為 c 1 c_1 c1,n所在的為 c 2 c_2 c2

則可以發現若 c 1 + c 2 c_1+c_2 c1+c2為奇數,先手勝。否則後手勝。

我們擴充套件到一般情況,若有多個聯通快,總共能加的邊數為 K K K(假設不在聯通快之間連邊)。

K K K為奇數,顯然答案可能不是先手!這是為什麼呢?

後手可以在塊與塊之間連邊,打破原來的規律!

怎麼解決?

假設在聯通快 i i i j j j之間連一條邊,我們冷靜分析就會發現:

  • c i ≡ c j ≡ 1 ( m o d    2 ) c_i≡c_j≡1(\mod 2) cicj1(mod2),則在i,j之間連一條邊之後會增加 c i ∗ c j c_i*c_j cicj條邊 ≡ 1 ( m o d    2 ) ≡1(\mod 2) 1(mod2),最後在減去連線的這條邊,奇偶性相當於沒變,相當於反轉了勝負。
  • 其他情況下奇偶性就會改變,和一個聯通快內的影響是一樣的。

這樣假設奇數塊有 Z Z Z個。

  • Z Z Z是奇數,反轉的次數為 ⌊ Z / 2 ⌋ \lfloor Z/2\rfloor Z/2
  • Z Z Z是偶數,反轉的次數為 [ Z / 2 , Z / 2 − 1 ] [Z/2,Z/2-1] [Z/2,Z/21]次(若最後剛好剩下1號塊和n號塊都是奇數)

這個 [ Z / 2 , Z / 2 − 1 ] [Z/2,Z/2-1] [Z/2,Z/21]對於比賽的勝負至關重要。

最終問題轉變成:

先手/後手能否使得最後1號塊和2號塊都為奇數?

看似任然不好做。

其實這存在一個非常優美的結論:

  • 若初始狀態1,2的奇偶性不一樣,則先手一定可以完成目標。
  • 若1,2初始都為1,則不管先後手都可以完成目標
  • 若1,2初始都為0,則不管先後手都不可能。

具體方法大概就是,若已經滿足要求就隨便拿兩個消掉,否則用一個實現目標。

Code:

/*
{
######################
#       Author       #
#        Gary        #
#        2020        #
######################
*/
//#pragma GCC target ("avx2")
//#pragma GCC optimization ("O3")
//#pragma GCC optimization ("unroll-loops")
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define rb(a,b,c) for(int a=b;a<=c;++a)
#define rl(a,b,c) for(int a=b;a>=c;--a)
#define LL long long
#define IT iterator
#define PB push_back
#define II(a,b) make_pair(a,b)
#define FIR first
#define SEC second
#define FREO freopen("check.out","w",stdout)
#define rep(a,b) for(int a=0;a<b;++a)
#define SRAND mt19937 rng(chrono::steady_clock::now().time_since_epoch().count())
#define random(a) rng()%a
#define ALL(a) a.begin(),a.end()
#define POB pop_back
#define ff fflush(stdout)
#define fastio ios::sync_with_stdio(false)
#define check_min(a,b) a=min(a,b)
#define check_max(a,b) a=max(a,b)
using namespace std;
const int INF=0x3f3f3f3f;
typedef pair<int,int> mp;
/*}
*/
const int MAXN=100000+20;
int n,m;
int fa[MAXN];
int cnt_node[MAXN],cnt_edge[MAXN],cnt[MAXN];
int root(int x){return fa[x]=(fa[x]==x? x:root(fa[x]));}
string result[2]={"First","Second"};
int cnt_1,cnt_0;
bool check(bool cnt_o,bool who){
	if(cnt_node[root(1)]==cnt_node[root(n)]){
		if(cnt_node[root(1)]==0){	
			return false;
		} 
		else{	
			return true;
		}
	}
	else{
		return (!who);
	}
}
void solve(){
	scanf("%d %d",&n,&m);
	rb(i,1,n)
		cnt_node[i]=cnt_edge[i]=cnt[i]=0,fa[i]=i;
	vector<mp> edges;
	rb(i,1,m){
		int u,v;
		scanf("%d %d",&u,&v);
		edges.PB(II(u,v));
		fa[root(u)]=root(v);
	}
	for(auto it:edges)
		cnt_edge[root(it.FIR)]++;
	rb(i,1,n)
		cnt_node[root(i)]^=1;
	bool rest=0;
	cnt_1=0,cnt_0=0;
	rb(i,1,n){
		cnt[root(i)]++;
		if(cnt_node[i])
			cnt_1+=cnt_node[i];
		else
			if(root(i)==i)
				++cnt_0;
	}
	rb(i,1,n){
		if(root(i)==i){
			rest^=(1ll*cnt[i]*(cnt[i]-1)/2-cnt_edge[i])&1;
		}
	}
	bool ok;
	int times=(cnt_1)/2;
	times&=1;
	if(cnt_1&1){
		if(times^rest){
			ok=0;
		}
		else{
			ok=1;
		}
	}
	else{
		if((times^rest)){
			ok=check(cnt_0&1,1);
		}
		else{
			ok=!check(cnt_0&1,0);
		}
	}
	cout<<result[ok]<<endl;
}
int main(){
	int t;
	cin>>t;
	while(t--){
		solve();
	}
	return 0;
}
/** 程式框架:
  *
  *
  *
  *
  **/

推薦閱讀:

AGC 045 D

CF 1369 F

AGC010 A,B,C,D,F

相關文章