CCPC2020 長春. H. Combination Lock(二分圖博弈 最大流ISAP)
題目鏈接
??沒好好復(fù)習(xí)以前學(xué)的,出了道模板真就看都看不出來。
總結(jié)一下二分圖匹配(原來真就只能出板子?),其它變型可以見JSOI2019 游戲、NOI2011 兔兔與蛋蛋游戲,還有一個板子。
條件:
操作可以視為在一個二分圖上進(jìn)行,每次從一部分走到另一部分,且不會走走過的點(diǎn)。
做法:
若某點(diǎn)(某狀態(tài))一定在最大匹配上,則處于該點(diǎn)時先手必勝;否則存在某種匹配使得該點(diǎn)不在最大匹配上,則處于該點(diǎn)時先手必?cái) #ㄗC明見JSOI2019 游戲)
判斷某點(diǎn)是否一定在最大匹配上:
做法1: 保留該點(diǎn)求最大匹配,刪掉該點(diǎn)再求最大匹配,看匹配數(shù)是否減少(做的時候反過來,先讓該點(diǎn)不與\(S/T\)相連求匹配數(shù),再加上該點(diǎn)到\(S/T\)的邊求(其它邊加上就行啊))。可以用網(wǎng)絡(luò)流復(fù)雜度\(O(能過)\)。
做法2: 先求最大匹配,若該點(diǎn)未被匹配則肯定是。然后枚舉所有未被匹配點(diǎn)\(x\),記與\(x\)有邊的點(diǎn)為\(to\),所有已與\(to\)匹配的點(diǎn)\(lk[to]\)可以不在最大匹配上。可以直接匈牙利,復(fù)雜度\(O(nm)\);或者網(wǎng)絡(luò)流后再DFS,復(fù)雜度\(O(能過+n+m)\)。
因?yàn)榉椒?要兩遍網(wǎng)絡(luò)流,比方法2的一遍慢的多(甚至慢一倍多?)。
\(Description\)
有一個有\(m\)位數(shù)字的鎖(每位為\(0\)到\(9\)),初始狀態(tài)給定。\(Alice,Bob\)輪流操作,每次操作可以更改一位數(shù)字(加一或減一),且改后狀態(tài)不能和之前出現(xiàn)過的重復(fù),且不能出現(xiàn)在給定的\(n\)種狀態(tài)中。\(Alice\)先手,不能操作的人輸,問誰能贏。
\(m\leq5,n\lt 10^m,10組數(shù)據(jù)\)。
\(Solution\)
每次操作后所有數(shù)位和的奇偶性都會改變,所以操作可以看做一個二分圖。
然后判斷起點(diǎn)是否一定在最大匹配上就ok了。
本地跑樣例老RE,原來是把擴(kuò)棧關(guān)了。。
//826ms 36000KB (做法1:2028ms 34200KB)
#include <bits/stdc++.h>
#define pc putchar
#define gc() getchar()
#define pb emplace_back
typedef long long LL;
const int N=1e5+5,M=N*20,LIM=1e5;
const int pw10[]={1,10,100,1000,10000,100000,1000000};
int Enum,S,T,H[N],nxt[M],fr[M],to[M],cap[M],pre[N],lev[N],lk[N];
bool odd[N],vis[N];
inline int read()
{
int now=0,f=1; char c=gc();
for(;!isdigit(c);c=='-'&&(f=-1),c=gc());
for(;isdigit(c);now=now*10+c-48,c=gc());
return now*f;
}
inline void AE(int u,int v,int w)
{
to[++Enum]=v, fr[Enum]=u, nxt[Enum]=H[u], H[u]=Enum, cap[Enum]=w;
to[++Enum]=u, fr[Enum]=v, nxt[Enum]=H[v], H[v]=Enum, cap[Enum]=0;
}
bool BFS()
{
static int q[N];
int T=::T;
for(int i=S; i<T; ++i) lev[i]=T+1;
int h=0,t=1; q[0]=T, lev[T]=0;
while(h<t)
{
int x=q[h++];
for(int i=H[x]; i; i=nxt[i])
if(lev[to[i]]==T+1 && cap[i^1])
lev[to[i]]=lev[x]+1, q[t++]=to[i];
}
return lev[S]<=T;
}
inline int Augment()
{
for(int i=T; i; i=fr[pre[i]])
--cap[pre[i]], ++cap[pre[i]^1];
return 1;
}
int ISAP()
{
static int cur[N],num[N];
if(!BFS()) return 0;
int S=::S,T=::T,res=0,x=S;
memset(num,0,T+1<<2);
for(int i=S; i<=T; ++i) cur[i]=H[i], ++num[lev[i]];
while(lev[S]<=T)
{
if(x==T) res+=Augment(),x=S;
bool can=0;
for(int i=cur[x]; i; i=nxt[i])
if(lev[to[i]]==lev[x]-1 && cap[i])
{
can=1, cur[x]=i, pre[x=to[i]]=i;
break;
}
if(!can)
{
int mn=T;
for(int i=H[x]; i; i=nxt[i])
if(cap[i]) mn=std::min(mn,lev[to[i]]);
if(!--num[lev[x]]) break;
++num[lev[x]=mn+1], cur[x]=H[x];
if(x!=S) x=fr[pre[x]];
}
}
return res;
}
void DFS(int x)
{
vis[x]=1;
for(int i=H[x]; i; i=nxt[i])
if(/*lk[to[i]] && */!vis[lk[to[i]]]) DFS(lk[to[i]]);
}
void Solve()
{
static int Ts=0; ++Ts;
static int ban[N];
int m=read(),n=read(),start=read();
Enum=1, S=0, T=pw10[m]+1;
while(n--) ban[read()]=Ts;
for(int i=0; i+1<T; ++i)
if(ban[i]!=Ts)
{
if(odd[i]) AE(i+1,T,1);
else AE(S,i+1,1);
}
int tmp=Enum;
for(int i=0; i+1<T; ++i)
if(ban[i]!=Ts && !odd[i])
for(int j=0; j<m; ++j)
{
int bit=i/pw10[j]%10,x;
if(bit!=9) x=i+pw10[j];
else x=i-9*pw10[j];
if(ban[x]!=Ts) AE(i+1,x+1,1);
if(bit) x=i-pw10[j];
else x=i+9*pw10[j];
if(ban[x]!=Ts) AE(i+1,x+1,1);
}
ISAP();
//--做法1:
// if(odd[start]) AE(start+1,T,1);
// else AE(S,start+1,1);
// if(ISAP()) puts("Alice");
// else puts("Bob");
//--做法2:
for(int i=tmp+1; i<=Enum; i+=2)
if(!cap[i]) assert(!lk[fr[i]]&&!lk[to[i]]),lk[fr[i]]=to[i], lk[to[i]]=fr[i];
vis[0]=1;//! DFS別走0。
for(int i=1; i<T; ++i) if(!lk[i] && !vis[i]) DFS(i);
puts(vis[start+1]?"Bob":"Alice");//vis=0:一定在最大匹配中
memset(H,0,T+1<<2), memset(lk,0,T+1<<2), memset(vis,0,T+1);
}
int main()
{
for(int i=0; i<LIM; ++i)
{
int x=i,t=0;
while(x) t+=x%10,x/=10;
odd[i]=t&1;
}
for(int T=read(); T--; Solve());
return 0;
}
別來無恙 你在心上
------------------------------------------------------------------------------------------------------------------------

浙公網(wǎng)安備 33010602011771號