「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)\) ,因此我们可以得到转移:
\(~~~~\) (\(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;
}