排列
设原来排列的置换环长度为 \(l_1,\dots l_m\)
\(v(i,j)\) 本质上就是将 \(i,j\) 所在的置换环拆开变成了长度为 \(l_{loc_i}+l_{loc_j}\) 的一个环,然后再将所有置换环长度求 \(\rm LCM\) 得到的结果;当然,如果同处一个置换环中就是 \(0\)
本质不同的交换只有环长度种类个,这时 \(\sqrt n\) 级别的,尝试快速计算交换后的结果:
对于每个质因子维护包含其的 \(l_i\) 含有的最大和次大的指数,即可在计算交换不同环长时得到增量
在杂糅两个长度相等的置换环时可以直接计算 \(2\) 的指数增量;对于 \(l_i,l_j\) 都包含的质因子可以只减少较大的一次,因为较小者必然是 \(l_i+l_j\) 的因子
Code Display
const int N=5e5+10;
int a[N],n,c[N],num[N],ton[N];
bool mark[N];
vector<pair<int,int> > Div[N];
int inv[N],ipw[N][21],pw[N][21];
int mx[N][2];
bool sec[N];
signed main(){
freopen("perm.in","r",stdin); freopen("perm.out","w",stdout);
n=5e5;
inv[0]=inv[1]=1;
for(int i=2;i<=n;++i){
inv[i]=mod-mul(mod/i,inv[mod%i]);
if(!Div[i].size()){
ipw[i][0]=pw[i][0]=1;
ipw[i][1]=inv[i];
pw[i][1]=i;
rep(j,2,20){
ipw[i][j]=mul(ipw[i][j-1],inv[i]);
pw[i][j]=mul(pw[i][j-1],i);
}
for(int j=i;j<=n;j+=i){
int tmp=j,e=0;
while(tmp%i==0) tmp/=i,++e;
Div[j].emplace_back(i,e);
}
}
}
int T; cin>>T;
while(T--){
scanf("%lld",&n);
rep(i,1,n) scanf("%lld",&a[i]),mark[i]=0;
int ans=0,m=0;
rep(i,1,n) if(!mark[i]){
int x=i,len=0;
while(!mark[x]) ++len,mark[x]=1,x=a[x];
c[++m]=len;
}
int typ=0;
sort(c+1,c+m+1);
rep(i,1,m){
if(c[i]!=c[i-1]) num[++typ]=c[i];
ton[c[i]]++;
}
int Lcm=1;
for(int i=1;i<=typ;++i){
for(auto t:Div[num[i]]){
if(mx[t.fir][0]<t.sec){
ckmul(Lcm,pw[t.fir][t.sec-mx[t.fir][0]]);
mx[t.fir][1]=mx[t.fir][0];
mx[t.fir][0]=t.sec;
}else if(mx[t.fir][1]<t.sec) mx[t.fir][1]=t.sec;
}
}
rep(i,1,typ){
rep(j,1,typ){
if(i==j){
if(ton[num[i]]==1) continue;
int curlcm=Lcm;
int tim=1;
if(num[i]!=1&&Div[num[i]][0].fir==2) tim+=Div[num[i]][0].sec;
if(tim>mx[2][0]) ckmul(curlcm,2);
ckadd(ans,mul(num[i]*num[i]%mod,ton[num[i]]*(ton[num[i]]-1)%mod*curlcm%mod));
}else{
int curlcm=Lcm;
if(ton[num[i]]==1){
for(auto t:Div[num[i]]){
if(mx[t.fir][0]==t.sec){
int de=mx[t.fir][0]-mx[t.fir][1];
ckmul(curlcm,ipw[t.fir][de]);
sec[t.fir]=1;
}
}
}
if(ton[num[j]]==1){
for(auto t:Div[num[j]]) if(!sec[t.fir]){
if(mx[t.fir][0]==t.sec){
int de=mx[t.fir][0]-mx[t.fir][1];
ckmul(curlcm,ipw[t.fir][de]);
}
}
}
for(auto t:Div[num[i]+num[j]]){
if(t.sec>mx[t.fir][sec[t.fir]]){
int de=t.sec-mx[t.fir][sec[t.fir]];
ckmul(curlcm,pw[t.fir][de]);
}
}
for(auto t:Div[num[i]]) sec[t.fir]=0;
for(auto t:Div[num[j]]) sec[t.fir]=0;
ckadd(ans,mul(num[i]*num[j]%mod,ton[num[i]]*ton[num[j]]%mod*curlcm%mod));
}
}
}
rep(i,1,m) ton[c[i]]--;
cout<<ans<<endl;
memset(mx,0,sizeof(mx));
}
return 0;
}
钥匙
将所有询问离线处理:在 \(\rm dfn\) 序上扫到 \(\rm dfn_u\) 时维护所有其他点的答案
对于每个钥匙找到和它 “配对” 的宝箱:对于每个颜色建立虚树再从每个钥匙开始 \(\rm DFS\) 并维护变量 \(c\),如果遇到同颜色宝箱则 \(c\leftarrow c-1\) ,遇到钥匙则 \(c\leftarrow c-1\) ,使 \(c\) 为 \(0\) 的箱子盒这个钥匙配对,同时可以找到对应的贡献联通块
注意这里由于询问终点的不同,每个钥匙可以存在于多个配对关系中
剩下的工作是一个二维数点,使用树状数组解决即可
Code Display
int n,m;
vector<int> G[N],nds[N],vt[N];
int dfn[N],ord[N],dep[N],fa[N],top[N],son[N],siz[N],tim;
inline void dfs1(int x,int fat){
dep[x]=dep[fa[x]=fat]+(siz[x]=1);
for(auto t:G[x]) if(t!=fat){
dfs1(t,x); siz[x]+=siz[t];
if(siz[t]>siz[son[x]]) son[x]=t;
}
return ;
}
inline void dfs2(int x,int topf){
top[x]=topf; ord[dfn[x]=++tim]=x;
if(son[x]) dfs2(son[x],topf);
for(auto t:G[x]) if(!dfn[t]) dfs2(t,t);
return ;
}
inline int get_lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]>dep[top[y]]) swap(x,y);
y=fa[top[y]];
}
return dep[x]<dep[y]?x:y;
}
inline int get_son(int x,int y){
if(dep[x]>dep[y]) swap(x,y);
int lst=0;
while(top[x]!=top[y]) y=fa[lst=top[y]];
return x==y?lst:son[x];
}
struct Fenwick_Tree{
int c[N];
inline int query(int x){
int res=0;
for(;x;x-=x&(-x)) res+=c[x];
return res;
}
inline void ins(int x,int v){
for(;x<=n;x+=x&(-x)) c[x]+=v;
return ;
}
}T;
int typ[N],col[N];
vector<tuple<int,int,int> > opt[N];
vector<pair<int,int> > qu[N];
int ans[N<<1];
int stk[N],stk_top;
int main(){
freopen("keys.in","r",stdin); freopen("keys.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d%d",&typ[i],&col[i]);
nds[col[i]].emplace_back(i);
}
for(int i=1;i<n;++i){
int u,v;
scanf("%d%d",&u,&v);
G[u].emplace_back(v);
G[v].emplace_back(u);
}
dfs1(1,0); dfs2(1,1);
for(int i=1;i<=n;++i) if(nds[i].size()){
sort(nds[i].begin(),nds[i].end(),[&](const int x,const int y){return dfn[x]<dfn[y];});
if(nds[i][0]!=1) nds[i].insert(nds[i].begin(),1);
stk_top=0;
auto ins_edge=[&](const int u,const int v){
vt[u].emplace_back(v);
vt[v].emplace_back(u);
return ;
};
for(auto x:nds[i]){
if(stk_top<=1){
stk[++stk_top]=x;
continue;
}
int lca=get_lca(stk[stk_top],x);
while(stk_top>1&&dfn[stk[stk_top-1]]>=dfn[lca]){
ins_edge(stk[stk_top],stk[stk_top-1]);
--stk_top;
}
if(stk[stk_top]!=lca) ins_edge(lca,stk[stk_top]),stk[stk_top]=lca;
stk[++stk_top]=x;
}
for(int j=1;j<stk_top;++j) ins_edge(stk[j],stk[j+1]);
int rt;
function<void(int,int,int)>dfs=[&](int x,int fat,int w){
if(col[x]==i){
if(typ[x]==2){
if(!(--w)){
if(dfn[rt]<=dfn[x]&&dfn[x]+siz[x]<=dfn[rt]+siz[rt]){
int y=get_son(rt,x);
opt[1].emplace_back(dfn[x],dfn[x]+siz[x]-1,1);
opt[dfn[y]].emplace_back(dfn[x],dfn[x]+siz[x]-1,-1);
if(dfn[y]+siz[y]<=n){
opt[dfn[y]+siz[y]].emplace_back(dfn[x],dfn[x]+siz[x]-1,1);
}
}else if(dfn[x]<=dfn[rt]&&dfn[rt]+siz[rt]<=dfn[x]+siz[x]){
int y=get_son(x,rt);
int ry=dfn[y]+siz[y]-1;
opt[dfn[rt]].emplace_back(1,dfn[y]-1,1);
if(ry<n) opt[dfn[rt]].emplace_back(ry+1,n,1);
opt[dfn[rt]+siz[rt]].emplace_back(1,dfn[y]-1,-1);
if(ry<n) opt[dfn[rt]+siz[rt]].emplace_back(ry+1,n,-1);
}else{
opt[dfn[rt]].emplace_back(dfn[x],dfn[x]+siz[x]-1,1);
opt[dfn[rt]+siz[rt]].emplace_back(dfn[x],dfn[x]+siz[x]-1,-1);
}
return ;
}
}else ++w;
}
for(auto t:vt[x]) if(t!=fat) dfs(t,x,w);
};
for(auto x:nds[i]) if(col[x]==i&&typ[x]==1) rt=x,dfs(x,0,0);
function<void(int,int)>clear=[&](const int x,const int fat){
for(auto t:vt[x]) if(t!=fat) clear(t,x);
vt[x].clear();
};
clear(1,1);
}
for(int i=1;i<=m;++i){
int s,e;
scanf("%d%d",&s,&e);
qu[dfn[s]].emplace_back(dfn[e],i);
}
for(int i=1;i<=n;++i){
for(auto o:opt[i]){
int l,r,v;
tie(l,r,v)=o;
T.ins(l,v);
T.ins(r+1,-v);
}
for(auto t:qu[i]) ans[t.sec]=T.query(t.fir);
}
for(int i=1;i<=m;++i) printf("%d\n",ans[i]);
return 0;
}
山河重整
如果出现了 \(\le i\) 被选中的数字 \(<i\) 那么这个序列不合法,若 \(i\) 不能被凑出来,选择的 \(<i\) 的元素个数一定不超过 \(\sqrt {2i}\)
使用容斥来计算序列数量:设 \(f_i\) 表示钦定第 \(i+1\) 个位置不能被凑出时不超过 \(i\) 的所有钦定形成的容斥系数总和
考虑使用 \(f_{j},j<i\) 来进行转移,此时的一个非常优美的性质是 \(\le j\) 的元素总和为 \(j\),那么要算的就是从 \([j+2,i]\) 中选出若干互异数字使得总和为 \(i-j\)
本题最精彩的一步是将数表进行翻转:设 \(g_i\) 为大于等于 \(i\) 的数字的个数,那么的 \(g_i<g_{i+1}\) 的 \(i\) 也是根号级别的,通过 \(g_i\) 的累加得到总和
设 \(dp_i\) 表示总和为 \(i\) 时划分的总方案数,从大到小枚举枚举划分成了几个元素,根据数表的变化得到转移形式,做 完全背包 就完成数字的增加
那么最终 \(f_i\) 的计算依赖一个形如 \(\rm CDQ\) 分治的过程,而 \(j> \frac i2\) 时不会产生贡献,所以也可以说成是倍增
倍增过程中仍然需要上面的 \(dp_i\) 来赋初值,而下标的处理可以找减去 \(i(j+2)\) 的 \(f\)
时间复杂度 \(\Theta(n\sqrt n)\)
Code Display
const int N=5e5+10;
int mod,n;
int f[N],g[N];
int pw[N];
inline void solve(int n){
if(n==1) return ;
solve(n>>1);
rep(i,0,n) g[i]=0;
int BLOCK=sqrt(n*2);
for(int i=BLOCK;i>=1;--i){
for(int j=n;j>=i;--j) g[j]=g[j-i];
for(int j=0;j+(j+2)*i<=n;++j) ckadd(g[j+(j+2)*i],f[j]);
for(int j=i;j<=n;++j) ckadd(g[j],g[j-i]);
}
for(int i=n/2+1;i<=n;++i) ckdel(f[i],g[i]);
return ;
}
signed main(){
freopen("rebuild.in","r",stdin); freopen("rebuild.out","w",stdout);
n=read(); mod=read();
pw[0]=1;
rep(i,1,n) pw[i]=add(pw[i-1],pw[i-1]);
for(int i=1000;i>=1;--i){
for(int j=n;j>=i;--j) f[j]=f[j-i];
f[i]=1;
for(int j=i;j<=n;++j) ckadd(f[j],f[j-i]);
}
f[0]=1;
solve(n);
int ans=pw[n];
for(int i=0;i<n;++i) ckdel(ans,mul(pw[n-i-1],f[i]));
print(ans);
return 0;
}
回忆
设 \(up_i\) 表示以 \(i\) 为结尾的研究中最浅的 \(s_i\) 的深度,这条链是必须要满足覆盖的
设 \(a_i\) 表示 \(i\) 的子树中已经完成配对的链数(起始终止点都在子树中),\(b_i\) 表示仍然时上下型链的数量,子树合并时可以贪心使得 \(b_i\) 最少
子树中若有多条链的起始点浅于子树的根,保留最浅的一个作为子树根新的 \(up\),余者在起点深度处打标记,回溯时将子树内造成的标记数量加入子树的 \(b_i\);若儿子节点的 \(up\) 是自身那么也作为儿子的 \(b_i\) 中的一个
子树内其他点的 \(up\) 都深于子树根,那么可以贪心地将 \(b_i\) 减一,没有则拆开一条已经配对的 \(a_i\)
Code Display
const int N=5e5+10;
int n,m,dep[N];
vector<int> G[N];
int a[N],b[N],mn[N],up[N],tag[N];
int main(){
freopen("memory.in","r",stdin); freopen("memory.out","w",stdout);
int T=read();
while(T--){
n=read(); m=read();
for(int i=1;i<n;++i){
int u=read(),v=read();
G[u].emplace_back(v);
G[v].emplace_back(u);
}
function<void(int,int)>dfs=[&](int x,int fat){
dep[x]=dep[fat]+1;
for(auto t:G[x]) if(t!=fat) dfs(t,x);
return ;
};
dfs(1,0);
for(int i=1;i<=m;++i){
int s=read(),e=read();
if(!up[e]||dep[s]<up[e]) up[e]=dep[s];
}
function<void(int,int)>Greedy=[&](int x,int fat){
for(auto t:G[x]) if(t!=fat){
Greedy(t,x);
b[t]+=tag[dep[x]];
tag[dep[x]]=0; // tags in the subtree of t
if(mn[t]==dep[x]) mn[t]=0,b[t]++;
if(mn[x]&&mn[t]){
if(mn[x]<mn[t]) tag[mn[t]]++;
else tag[mn[x]]++,mn[x]=mn[t];
}else{
mn[x]=mn[x]+mn[t];
}
if(b[x]<b[t]){
swap(a[x],a[t]);
swap(b[x],b[t]);
}
if(b[t]+2*a[t]>=b[x]){
int sum=b[x]+b[t]+2*a[t];
a[x]+=sum/2;
b[x]=sum&1;
}else{
b[x]-=b[t]+a[t]*2;
a[x]+=b[t]+a[t]*2;
}// divide all chains with existance
}
if(up[x]){
if(!mn[x]){
mn[x]=up[x];
if(b[x]) --b[x];
else if(a[x]) --a[x],++b[x];
}else ckmin(mn[x],up[x]);
}
return ;
};
Greedy(1,0);
printf("%d\n",a[1]+b[1]);
rep(i,1,n){
G[i].clear();
a[i]=b[i]=tag[i]=mn[i]=up[i]=dep[i]=0;
}
}
return 0;
}