题解:CF2063F2 Counting Is Not Fun (Hard Version)
提供一个疑似是本题最简单的做法,并且我们爆标了。practical 的复杂度是
感谢 @StayAlone 提供的细节帮助,感谢 @AfterFullStop 指出本做法可以做到线性。
回顾 F1 的做法:
我们对已知的匹配括号建立括号串分治树。具体来说,树上每个节点都是一对匹配括号
有了树结构,答案是容易表示的。我们维护每个节点下没有被儿子匹配管辖的空位的长度
答案把所有节点的贡献乘起来就好。
延续 F1 的做法。我们尝试维护分治树,考虑每次加入一个节点产生的影响。因为加入节点需要操作子树,这不太方便,我们考虑时光倒流。
时光倒流之后,每次我们只需删除一个节点,把它的儿子挂到它的父亲上。贡献上会产生的影响只有删去它的
我们直接用逆元撤销掉原来的贡献,再把新的贡献加上去,同时维护
考虑剩下的问题是我们还需要也仅需要维护分治树上的父亲关系,需要支持删除一个节点。类似可并堆的处理办法,考虑我们没必要真实删除这个节点,把它重定向到它的父亲即可。用一个并查集就可以简单维护好。
实现上,因为分治树上所有节点的端点都互不相同,所以我们可以从端点映射节点规避 map;卡特兰数及其逆元可以用线性阶乘逆元、线性逆元
树上并查集直接写可以做到
下面是一个懒得写按秩合并所以带
int tc;
int n;
ll fac[N],ifac[N],inv[N];
ll qpow(ll b,int p){
if(!p) return 1;
ll d=qpow(b,p>>1);
if(p&1) return d*d%mod*b%mod;
else return d*d%mod;
}
void init(int n=1.2e6){
fac[0]=1;
fr1(i,1,n) fac[i]=fac[i-1]*i%mod;
ifac[n]=qpow(fac[n],mod-2);
fr2(i,n-1,0) ifac[i]=ifac[i+1]*(i+1)%mod;
fr1(i,1,n) inv[i]=ifac[i]*fac[i-1]%mod;
}
ll Ca(int n){
return fac[n]*ifac[n>>1]%mod*ifac[n>>1]%mod*inv[(n>>1)+1]%mod;
}
ll iCa(int n){
return ifac[n]*fac[n>>1]%mod*fac[n>>1]%mod*((n>>1)+1)%mod;
}
int mat[N];
vector <int> op;
vector <int> res;
int fa[N],dirf[N];
int len[N];
int node[N];
int tot;
int find(int x){
if(fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
void divide(int f,int l,int r){
tot++;
node[l]=tot;
len[tot]=0,dirf[tot]=f,fa[tot]=tot;
int cnt=0;
fr1(i,l+1,r-1){
divide(node[l],i,mat[i]);
i=mat[i];
}
}
void solve(){
tot=0;
op.clear(),res.clear();
cin>>n;
fr1(i,1,(n<<1)) mat[i]=0;
fr1(i,1,n){
int u,v;
cin>>u>>v;
mat[u]=v;
op.pb(u);
}
divide(0,0,(n<<1)+1);
ll ans=1;
fr2(i,n-1,0){
res.pb(ans);
int id=op[i];
int u=node[id];
int f=find(dirf[u]);
(ans*=iCa(len[u]))%=mod;
(ans*=iCa(len[f]))%=mod;
len[f]+=len[u]+2;
(ans*=Ca(len[f]))%=mod;
fa[u]=f;
}
res.pb(ans);
while(!res.empty()){
cout<<res.back()<<" ";
res.pop_back();
}
cout<<'\n';
}