[2018.5.24集训]山景城-博弈论
Posted zltttt
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[2018.5.24集训]山景城-博弈论相关的知识,希望对你有一定的参考价值。
题目大意
绝顶聪明的A和B在一棵树上博弈。
A的目标是进行最少次数的操作,使B到达节点t。每回合A可以进行三种决策:
1.不操作(这不算操作次数) 2.切断一条树上的边 3.消除一条边上B留下的标记。
B初始位于节点s,目标是在到达t之前使A进行尽可能多的操作。
每回合,若B所处节点存在没有被切断也没有留下标记的边,则B必须从这些边之中选择一条走过去,并在这条边上做标记,否则原地不动。
求A的最终操作次数。
部分分:s和t之间有一条边。
题解
假定令t为树根。
首先考虑部分分。
观察可知,最后B一定会走到一个死路,然后A在封锁B所在节点到t上的其他边后,沿路进行消除标记操作强行让B到达t。
并且,A在B走到死路之前进行的消除标记操作是不优的,会导致B的下一步的选择变多,即使不会改变最优答案,将来再消除和现在消除的总次数也是一样的,该切断的边也还是要切,不如优先假定消除标记操作全部在最后进行。
然后考虑一个dp:设 $f_i$ 代表B第一次来到 $i$ 节点,并被A强制送回 $i$ 节点这段时间内,A的总操作次数。
那么显然每次A会选择切掉当前点和子树中 $f$ 值最大的点相连的边,而B会选择次大值向下走。
同时,在将B引到当前节点时,应确保当前节点周围的边除了通往t的边和B到达这个位置的边之外的边全部被切断。因此有转移方程:
$$f_u = deg_u-2 + \mathrm{second?max}_{v \in child_u}{f_v}$$
最后得到的 $f_s$ 即为部分分答案!
对于原问题,令可以发现B多出了操作:向上走,而A不能切断s到t之间的边,否则B将无法到达t。
于是只能让B随意往上走,但一旦中途B选择进入某个儿子,B下一次在链上就是被A强制移动了。
首先对这条链上的每个节点进行一次dp,接下来的操作包括这次dp均不考虑这些点在s-t链上的儿子。
在dp结束后,给当前点的每个儿子的 $f$ 值加上它到t路径上所节点的儿子数量,因为要防止B再次进入另一个儿子从而带来负收益。
因此,现在每个链上节点的儿子的 $f$ 值,代表了若B选择进入当前儿子后,剩余需要完成目标的花费。
直接对链dp不太好做,那么考虑二分答案。
考虑如何判断一个答案是否合法:
从s到t依次枚举链上的节点,令s为1号节点,则在B到达 $i$ 号节点并做出下一步决策前,$A$ 可以有 $i$ 次操作机会。
假如当前二分值为 $lim$。
设 $x$ 为为了使答案合法,目前为止至少需要使用的操作次数。
对于节点 $i$ 的儿子 $v$,若 $x+f_v>lim$,则在B在 $i$ 节点上并还未做出下一步决策之前,$x$ 与 $v$ 之间的边必须切断,否则若B选择进入 $v$ ,答案将不合法。
于是,判定条件为,在扫完 $i$ 的所有儿子后,$x$ 加上 $i$ 需要切断的儿子数,若 $x>i$ 或 $x>lim$,则 $lim$ 不合法。
于是二分一发就完成了~
代码:
#include<cstdio>
#include<vector>
#include<cstdlib>
#include<algorithm>
using namespace std;
inline int read()
{
int x=0;char ch=getchar();
while(ch<'0' || '9'<ch)ch=getchar();
while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
return x;
}
const int N=1e6+9;
int n,s,t,p,las;
vector<int> g[N];
int f[N],fa[N];
int stk[N],top;
inline void dfs(int u)
{
for(int i=0,v;i<g[u].size();i++)
if((v=g[u][i])!=fa[u])
{
fa[v]=u;
dfs(v);
}
}
inline void dfs2(int u)
{
if(!g[u].size())
{
f[u]=0;
return;
}
int mx1=0,mx2=0;
for(int i=0;i<g[u].size();i++)
{
dfs2(g[u][i]);
if(f[g[u][i]]>=mx1)
mx2=mx1,mx1=f[g[u][i]];
else if(f[g[u][i]]>mx2)
mx2=f[g[u][i]];
}
f[u]=mx2+g[u].size();
}
namespace faq
{
inline bool check(int x)
{
int must=0;
for(int i=1;i<=top;i++)
{
int cnt=0;
for(int j=0;j<g[stk[i]].size();j++)
if(f[g[stk[i]][j]]+must>x)
cnt++;
must+=cnt;
if(must>i || must>x)return 0;
}
return 1;
}
int mina()
{
dfs2(s);
stk[top=1]=s;
for(int x=fa[s],las=s;x!=t;las=x,x=fa[x])
{
stk[++top]=x;
g[x].erase(find(g[x].begin(),g[x].end(),las));
dfs2(x);
}
int tot=0;
for(int i=top;i>=1;i--)
{
tot+=g[stk[i]].size();
for(int j=0;j<g[stk[i]].size();j++)
f[g[stk[i]][j]]+=tot;
}
int l=0,r=n,mid,ans=n;
while(l<=r)
{
mid=l+r>>1;
if(check(mid))
ans=mid,r=mid-1;
else
l=mid+1;
}
printf("%d\n",ans);
return 0;
}
}
int main()
{
n=read();t=read();s=read();
for(int i=2,u,v;i<=n;i++)
{
u=read();v=read();
g[u].push_back(v);
g[v].push_back(u);
}
if(s==t)return puts("0"),0;
dfs(t);return 0;
for(int i=1;i<=n;i++)
if(i!=t)g[i].erase(find(g[i].begin(),g[i].end(),fa[i]));
return faq::mina();
}
以上是关于[2018.5.24集训]山景城-博弈论的主要内容,如果未能解决你的问题,请参考以下文章