专栏文章
绝世好题
P13336题解参与者 3已保存评论 3
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 3 条
- 当前快照
- 1 份
- 快照标识符
- @minxwlt2
- 此快照首次捕获于
- 2025/12/02 10:09 3 个月前
- 此快照最后确认于
- 2025/12/02 10:09 3 个月前
神题,笔者并没有找到除官解外的题解,也并没有找到任何有人通过本题的证据(不过似乎是 2015 集训队作业),就来记录一下官方题解神仙做法,给个证明。
首先你发现有解答案上界是 ,暴力模拟状态 和 (代表当前每个点经过的奇偶性和当前在那个点)即可。
然后?你不会了看了眼 tag 折半搜索,你尝试把点分成两部分,对两边预处理当前部分状态为 ,在点 ,走到另外一部分时的 和 ,但你发现你的状态数是没有变化的,似乎还是 的。
接下来就是很厉害的想法了。
官解告诉我们保留所有边,直接建反图从 bfs 给每个点标记一个 ,把没有 bfs 到的扔掉(显然走到那些点不可能有解),然后把 较小的那一半分到一个集合 ,剩下的分到另外一个集合 。
它告诉我们只用预处理 集合内从状态为 ,在点 ,走到另外一部分时的 和 ,在 时利用这个东西加速跳,在 时暴力跳就是正确的,在到达 前是不会出现重复的 集合奇偶状态的(如果跳到了那些没有 bfs 的点就无解)!
很反直觉不是吗?
然而官解并没有给出证明,这里我们证明一下。
考虑我现在 集合跳跃状态为 ,在 中某个点 ,我现在想要转一圈后 状态仍然是 。那么说明我从现在起 的两条边都会走过(现在会走某条边,之后为了消除本次的奇偶翻转还会再走一次),由于它的 是由它的两条出边指向的点更新的,说明它在重复状态前必然会访问到一个 的点,由于我们是把 小的分 内,这个点必然是 内的,继续从 的点出发考虑,我们就发现必然会经过 的点,也就是必然会经过点 !
所以我们在 跳的次数是不超过 次的(官解只给到了 ,笔者认为是不可能 一样, 不同的,上述证明已经说明了这一点,如果笔者给的上界有误请指出),而 状态数是 的,所以 取 左右跑得最快。
参考代码:
CPP#include<bits/stdc++.h>
#define ll long long
#define INF 214748364719260817ll
using namespace std;
ll t,n;
ll l[45],r[45];
ll to[45],td[45];
ll dep[45];
vector<ll>bk[45];
vector<ll>a,b;
struct px
{
ll nS,nu,c;
};
px dp[1<<18][18];
bool vis[1<<18][18];
px dfs(ll S,ll u)
{
if(to[u]!=1)return (px){S,u,0};
if(vis[S][td[u]])return dp[S][td[u]];
vis[S][td[u]]=1;
ll nu=u;
if((S>>td[u])&1)nu=r[u];
else nu=l[u];
dp[S][td[u]]=dfs(S^(1<<td[u]),nu);
++dp[S][td[u]].c;
return dp[S][td[u]];
}
ll get_ans(ll aS,ll bS,ll u)
{
if(u==n-1)return 0;
if(!to[u])return -INF;
if(to[u]==1)
return dp[aS][td[u]].c+get_ans(dp[aS][td[u]].nS,bS,dp[aS][td[u]].nu);
else
{
ll nu=u;
if((bS>>td[u])&1)nu=r[u];
else nu=l[u];
return 1+get_ans(aS,bS^(1<<td[u]),nu);
}
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);
cin>>t;
for(ll sb=1;sb<=t;++sb)
{
cin>>n;
memset(to,0,sizeof(to));
memset(dep,0,sizeof(dep));
memset(vis,0,sizeof(vis));
a.clear();b.clear();
for(ll i=0;i<n;++i)bk[i].clear();
for(ll i=0;i<n-1;++i)cin>>l[i]>>r[i],--l[i],--r[i];
for(ll i=0;i<n-1;++i)bk[l[i]].emplace_back(i),bk[r[i]].emplace_back(i);
queue<ll>q;
q.emplace(n-1);
dep[n-1]=1;
while(!q.empty())
{
ll id=q.front();q.pop();
a.emplace_back(id);
for(auto it:bk[id])
{
if(!dep[it])
dep[it]=dep[id]+1,q.emplace(it);
}
}
cout<<"Case #"<<sb<<": ";
if(a.size()<2||!dep[0])
{
cout<<"Infinity\n";continue;
}
ll where=a.size()/2;
for(ll i=a.size()-1;i>=max(22ll,where);--i)b.emplace_back(a.back()),a.pop_back();
swap(a,b);
ll cot=0;
for(auto it:a)to[it]=1,td[it]=cot++;
cot=0;
for(auto it:b)to[it]=2,td[it]=cot++;
for(ll i=0;i<(1<<a.size());++i)
for(ll j=0;j<a.size();++j)
if(!vis[i][j])
dfs(i,a[j]);
ll res=get_ans(0,0,0);
if(res<0)cout<<"Infinity\n";
else cout<<res<<'\n';
}
}
相关推荐
评论
共 3 条评论,欢迎与作者交流。
正在加载评论...