1. 程式人生 > 其它 >「CEOI2017」Mousetrap

「CEOI2017」Mousetrap

[CEOI2017]Mousetrap 題解

題意

\(~~~~\) 我並不能概括,所以這個板塊只是來走過場的。

真實用意是來開 題目傳送門

題解

\(~~~~\) 終於過來補檔了,很妙的題(不看題解就想不出來,來記錄一下。

\(~~~~\) 將陷阱房當作整棵樹的根,那麼老鼠就會盡量向下走,同理管理者會盡可能阻止老鼠這一行為。

\(~~~~\) 根據上面的說法,管理者的操作實質是阻止老鼠,因此管理者相對來說處於被動,那我們來試著模擬老鼠會有什麼行動。我們發現,一旦老鼠開始朝遠離根的方向走,那麼除非管理員擦乾淨它,老鼠不可能再走上來,以此類推,它最終會進入一個葉結點而顯然,管理員會在做好準備後再擦掉弄髒的邊,即之後老鼠的行為一定是朝著陷阱房去。

\(~~~~\) 因此,我們現在可以知道老鼠的策略:選擇進入一個葉子結點,困在裡面等待管理員操作,向上走到陷阱房。現在先不考慮老鼠如何選擇葉子結點,假如老鼠已經把自己封在了一個葉子結點,那管理員需要做的顯然就是把該葉子結點到陷阱房的所有支路全部封死,然後給老鼠擦路。

\(~~~~\) 為什麼這是最優的呢?首先,擦路部分是必須的,這樣才能保證老鼠能走到陷阱房。其次,如果我們少封一條支路,那麼至少我們需要再用一次操作給老鼠擦路,而如果老鼠還要繼續往下走,需要的運算元顯然會更多。

\(~~~~\) 至此,我們得到結論一:當老鼠進入葉節點,管理者的最優策略是封死其他支路,然後擦乾淨路。而老鼠為使管理者操作次數更多,能做的只有選擇進入哪個葉子結點。假設我們已經知道了老鼠走到 \(i\)

結點並進入其子樹,又回到 \(i\) 結點需要的管理者的最優操作次數,記作 \(f_i\)

當老鼠進入葉節點,管理者的最優策略是封死其他支路,然後擦乾淨路只有選擇進入哪個葉子結點。一個葉子結點會造成哪些操作上的不同呢?首先,從葉子結點

\(~~~~\) 那麼顯然,我們是可以根據一個點其子結點的 \(f\) 來獲得該點的 \(f\) 的,舉個例子。

\(~~~~\) 如果我們已經知道 \(f_2,f_3,f_4\) ,那我們需要的操作次數是什麼呢?

\(~~~~\)\(f_2>f_3>f_4\) ,那麼老鼠一定會想走 \(2\) 結點,但注意到我們是先手,因此我們會想堵住 \((1,2)\)

,那麼老鼠剩下的選擇就是走 \(3\) ,會用 \(f_3\) 的操作次數,同時我們還要堵住 \((1,4)\) ,然後擦掉 \((1,3)\) ,因此我們可以得到轉移:

\[\large f_i=次大值_{j\in son_i} f_j+deg_i-1 \]

\(~~~~\)\(deg_i\) 表示 \(i\) 點的度數,\(-1\) 是去掉 \((i,fa_i)\) 這條邊)

\(~~~~\) 然後,我們還要再處理一個點到根節點的分支數量 \(bra_i\) ,這可以簡單地處理出來,這裡不過多贅述。因此,當老鼠自 \(i\) 的父親來到 \(i\) 時,之後它還需要 \(bra_{fa_i}+f_i-[fa_i \not= m]\) 步走出(\(m\) 同題目含義),比如下圖,上面 \(-1\) 就是因為 \(bra_{f_i}\) 中還包含了 \(fa_i\)\(m\) 之間的一條邊,這條邊在上來時就被弄髒了,不用堵。這裡把來到 \(i\) 後還需要的步數記作 \(need_i\)

\(~~~~\) 上面的一堆 \(dp\) 做的準備就是為了決定老鼠將會進入哪個子樹。而題目提到需要在對方試圖最大化操作次數的情況下求最小值,所以可以考慮二分。(強行扯到二分

\(~~~~\) 判定在 \(k\) 次操作能否將老鼠趕到陷阱,那我們需要做的就是判定老鼠能否進入某個 \(need>k\) 的子樹。

\(~~~~\) 因此,判定老鼠向上將會所在的結點周邊是否有 \(need>k\) 的點,若有且無法被封掉則會使得判定失敗。同時注意一回合只能封住一條路,並且封路也會使得 \(k\) 減小。

\(~~~~\) 然後這道題結束了。

程式碼

檢視程式碼
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    x*=f;
}
int n,rt,m;
vector <int> G[1000005];
int sta[1000005],Top=0;
int deg[1000005],fa[1000005],f[1000005],s[1000005];
void dfs(int u,int Las,int bra)
{
	fa[u]=Las;
	int m1=0,m2=0;
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		if(v==Las) continue;
		else if(u!=rt) dfs(v,u,bra+deg[u]-2);
		else dfs(v,u,bra);
		if(f[v]>m1) m2=m1,m1=f[v];
		else if(f[v]>m2) m2=f[v];
	}
	f[u]=m2+deg[u]-2+1;
	s[u]=bra+f[u]+(fa[u]==m);
} 
inline bool check(int k)
{
	int Have=k,CanUse=0,Las=-1;
	for(int i=1;i<Top;i++)
	{
		int u=sta[i];
		CanUse++; 
		int tmp=0;//平衡這個點 
		for(int j=0;j<G[u].size();j++)
		{
			int v=G[u][j];
			if(v==fa[u]||v==Las) continue;
			if(s[v]>Have+tmp) tmp++,CanUse--,Have--;//如果老鼠走這個點你就無法完成 
		}
		if(Have<0||CanUse<0) return false;
		Las=u;
	}
	return true;
}
int main() {
	read(n);read(rt);read(m);
	for(int i=1,u,v;i<n;i++)
	{
		read(u);read(v);
		deg[u]++;deg[v]++;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(rt,0,0);
	int x=m;
	while(fa[x])
	{
		sta[++Top]=x;
		x=fa[x];
	}
//	for(int i=1;i<=n;i++) printf("%d %d\n",f[i],s[i]);
	register int l=s[m],r=n+10,mid,ans;
	while(l<=r)
	{
		mid=(l+r)>>1;
		if(check(mid)) r=mid-1,ans=mid;
		else l=mid+1; 
	}
	printf("%d",ans);
	return 0;
}