Problem
Solution
IOI的题很适合按subtask做
先考虑子任务3,对于每个触发器后面接的触发器,如果有4个,可以设计一个完全二叉树的结构,如果仅有3个只需要把XX这条边指向自己即可。这样构造出的开关不会超过 N N N。
再考虑子任务4,N是2的整数次幂。不难发现子任务3的方法浪费了很多开关,不妨尝试对整个序列构建完全二叉树结构,把起点指向第一个触发器,所有触发器指向完全二叉树的根。
我们可以把同一层的深度的点向X走视作0,向Y走视作1,那么其实每走一次就相当于给当前代表的数进行+1操作,然后输出它的二进制串的反串能走到的地方,即第 i i i 次取出的节点就是 FFT \text{FFT} FFT 中的 r e v [ i ] rev[i] rev[i] 。不难发现我们会在这棵树中走完 0 ∼ 2 k − 1 0\sim 2^k-1 0∼2k−1,就保证了每个节点都被走过偶数次。
考虑子任务5,构建的完全二叉树中有很多叶子都是空的,一个显然的想法是如果一个开关子树内都是空的,我们可以直接把它指向根。为了节省更多的开关,我们会希望能把它集中在一起,那么我们就只取最右边的那些叶子即可。
计算一下,子树内没有空叶子的开关最多仅有 N N N 个,每层最多仅有1个不满的开关,因此答案不会超过 N + log 2 N N+\log_2 N N+log2N。
Code
#include "doll.h"
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;
typedef long long ll;
typedef vector<int> vec;
const int maxn=300010;
template <typename Tp> int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> void read(Tp &x)
{
x=0;char ch=getchar();int f=0;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+(ch-'0'),ch=getchar();
if(f) x=-x;
}
int n,N,l,tot,rt,rev[maxn],t[maxn],to[maxn];
vec c,x,y;
vector<int>::iterator itr;
int build(int l,int r)
{
if(l==r) return l>=N-n?to[l]:-N;
int m=(l+r)>>1,lc,rc;
lc=build(l,m);rc=build(m+1,r);
if(lc==-N&&rc==-N) return -N;
x.push_back(lc);y.push_back(rc);
return -x.size();
}
void create_circuit(int m,vec a)
{
n=a.size();a.push_back(0);
for(N=1,l=0;N<n;N<<=1) ++l;
for(int i=0;i<N;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
memset(t,0xff,sizeof(t));
for(int i=N-n;i<N;i++) t[rev[i]]=i;
for(int i=0;i<N;i++) if(~t[i]) to[t[i]]=a[++tot];
rt=build(0,N-1);c.push_back(a[0]);
for(int i=1;i<=m;i++) c.push_back(rt);
for(itr=x.begin();itr!=x.end();++itr) if(*itr==-N) *itr=rt;
for(itr=y.begin();itr!=y.end();++itr) if(*itr==-N) *itr=rt;
answer(c,x,y);
}