P12421
注意事项
这是一道交互题,注意刷新缓存区。
对于 C++ 语言,推荐使用 endl 换行以刷新缓存区。
题目大意
给出
解法
不妨称交互库的两种行为分别为“移动”或“回复”。
观察不难发现“移动”将会带来更少的信息,首先考虑如果交互库一直“移动”我们要如何应对。
不难发现我们只要每次指定相同的
那么如果某次交互库选择“回复”,那么我们不可能再次操作
于是一旦某次交互库选择“回复”,那么我们考虑在接下来的操作中只需维护
接下来从
经过以上预处理后即可重复以下过程直到返回答案:
- 若
h_v=t ,则直接返回v 。 - 否则,枚举
v 的每个w_p 非0 的子节点p 并检查s 是否在p 子树内。- 若
p 是最后一个被检查的子节点,直接将v 置为p 并返回第1 步。 - 否则不断对
p 操作直到进行了t-h_v+1 次或交互库选择了“回复”,期间交互库一定选择了“移动”,此时将t 置为t-1 即可。 - 结束对
p 的操作后,有以下几种情况:- 进行了
t-h_v+1 次操作后交互库仍未选择“回复”:此时直接返回p 即可。 - 在第
t-h_v+1 次操作时交互库才选择“回复”:此时若d=0 ,返回p ;否则d=1 ,返回v 。 - 交互库提前选择了“回复”:此时若
d=t-h_v-1 ,则将v 置为p 并返回第1 步;否则d=t-h_v+1 ,则可以判定s 不在p 子树内,继续枚举下一个p 即可。
- 进行了
- 若
不难发现上述过程一定能正确求解出
次数分析
不难发现上述做法在某些情况下次数可能达到
不难发现当
那么接下来分析交互次数:
- 一开始操作点
u 时,交互库每“移动”一次,至少有1 个节点被排除了。(除非当前可能的s 只剩u ,但是这种情况并不重要,因为反正最多也就n-1 次操作) - 若交互库第一次“回复”时就只有一个可能的
s ,那么此时我们显然不会再进行任何更多的操作,所以这种情况也是合标的。 - 否则如果上述条件均成立(
u 为叶节点且交互库第一次“回复”时有多于一个可能的s ),那么第一次“回复”时至少排除了2 个点(u 和与u 相邻的唯一的点) - 接下来每步操作几乎都至少排除了
1 个点:交互库始终提前选择“回复”,那么这里每次“移动”都等价于是最开始操作点u 时的移动,而每次“回复”都能至少排除1 个节点,由于最后还剩1 个节点,所以这里至多就是n-2 次操作(第一次“回复”时多排除了1 个点)。 - 否则一旦出现进行第
t-h_v+1 次操作的情况就会立刻求解出答案,所以这里至多比上面多出1 步,也就是n-1 步。
综上,本做法完全满足本题的要求,且时空复杂度均容易做到
参考代码
这是我的赛时 AC 记录中的代码。
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int T,I,n,m,u[110000],v[110000],num[110000],*mp[110000],deep[110000],siz[110000],fa[110000],d,i,j,op,x,o,pans,t;
void dfs(int x)
{
int i;
deep[x]=deep[fa[x]]+1;
for(i=1;i<=mp[x][0];i++)
if(mp[x][i]!=fa[x])
{
fa[mp[x][i]]=x;
dfs(mp[x][i]);
siz[x]=siz[x]+siz[mp[x][i]];
}
if(deep[x]==d)
siz[x]++;
}
main()
{
cin>>T;
for(I=1;I<=T;I++)
{
cin>>n>>m;
for(i=1;i<n;i++)
cin>>u[i]>>v[i];
for(i=1;i<n;i++)
{
num[u[i]]++;
num[v[i]]++;
}
for(i=1;i<=n;i++)
{
mp[i]=new int[num[i]+1];
mp[i][0]=0;
}
for(i=1;i<n;i++)
{
mp[u[i]][0]++;
mp[u[i]][mp[u[i]][0]]=v[i];
mp[v[i]][0]++;
mp[v[i]][mp[v[i]][0]]=u[i];
}
for(i=1;i<=n;i++)
if(num[i]<=1)
break;
o=i;
for(i=1;i<=n-1;i++)
{
cout<<"? "<<o<<endl;
cin>>op;
if(op==2)
{
cin>>x;
break;
}
}
if(i>n-1)
{
cout<<"! "<<o<<endl;
cin>>x;
for(i=1;i<=n;i++)
{
num[i]=0;
delete [] mp[i];
}
continue;
}
d=x+1;
fa[o]=0;
dfs(o);
pans=siz[o];
while(1)
{
if(deep[o]==d)
{
cout<<"! "<<o<<endl;
cin>>x;
break;
}
for(i=1;i<=mp[o][0];i++)
if(mp[o][i]!=fa[o]&&siz[mp[o][i]]>0)
if(siz[mp[o][i]]==pans)
{
o=mp[o][i];
break;
}
else
{
t=d-deep[o]+1;
for(j=1;j<=t;j++)
{
cout<<"? "<<mp[o][i]<<endl;
cin>>op;
if(op==1)
d--;
else
{
cin>>x;
if(x==d-deep[mp[o][i]])
{
pans=siz[mp[o][i]];
o=mp[o][i];
op=3;
break;
}
if(j==t)
{
if(x==0)
cout<<"! "<<mp[o][i]<<endl;
else
cout<<"! "<<o<<endl;
cin>>x;
op=4;
break;
}
break;
}
}
if(op>2)
break;
if(j>t)
{
cout<<"! "<<mp[o][i]<<endl;
cin>>x;
op=4;
break;
}
pans=pans-siz[mp[o][i]];
}
if(op==4)
break;
}
for(i=1;i<=n;i++)
{
siz[i]=0;
num[i]=0;
delete [] mp[i];
}
}
}
碎碎念
怎么和官方题解做法完全不一样?
别忘了刷新缓存区,多测别忘清,别忘记读入每组数据给出答案后的 1。
赛时调了好久最后发现是 lca 挂了,一怒之下把 lca 删了,发现做法还是对的。(猜猜我一开始哪里用了 lca)