【考试总结】2022-06-06

排列

设原来排列的置换环长度为 l1,lm

v(i,j) 本质上就是将 i,j 所在的置换环拆开变成了长度为 lloci+llocj 的一个环,然后再将所有置换环长度求 LCM 得到的结果;当然,如果同处一个置换环中就是 0

本质不同的交换只有环长度种类个,这时 n 级别的,尝试快速计算交换后的结果:

对于每个质因子维护包含其的 li 含有的最大和次大的指数,即可在计算交换不同环长时得到增量

在杂糅两个长度相等的置换环时可以直接计算 2 的指数增量;对于 li,lj 都包含的质因子可以只减少较大的一次,因为较小者必然是 li+lj 的因子

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;
}


钥匙

将所有询问离线处理:在 dfn 序上扫到 dfnu 时维护所有其他点的答案

对于每个钥匙找到和它 “配对” 的宝箱:对于每个颜色建立虚树再从每个钥匙开始 DFS 并维护变量 c,如果遇到同颜色宝箱则 cc1 ,遇到钥匙则 cc1 ,使 c0 的箱子盒这个钥匙配对,同时可以找到对应的贡献联通块

注意这里由于询问终点的不同,每个钥匙可以存在于多个配对关系中

剩下的工作是一个二维数点,使用树状数组解决即可

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;
}

山河重整

如果出现了 i 被选中的数字 <i 那么这个序列不合法,若 i 不能被凑出来,选择的 <i 的元素个数一定不超过 2i

使用容斥来计算序列数量:设 fi 表示钦定第 i+1 个位置不能被凑出时不超过 i 的所有钦定形成的容斥系数总和

考虑使用 fj,j<i 来进行转移,此时的一个非常优美的性质是 j 的元素总和为 j,那么要算的就是从 [j+2,i] 中选出若干互异数字使得总和为 ij

本题最精彩的一步是将数表进行翻转:设 gi 为大于等于 i 的数字的个数,那么的 gi<gi+1i 也是根号级别的,通过 gi 的累加得到总和

dpi 表示总和为 i 时划分的总方案数,从大到小枚举枚举划分成了几个元素,根据数表的变化得到转移形式,做 完全背包 就完成数字的增加

那么最终 fi 的计算依赖一个形如 CDQ 分治的过程,而 j>i2 时不会产生贡献,所以也可以说成是倍增

倍增过程中仍然需要上面的 dpi 来赋初值,而下标的处理可以找减去 i(j+2)f

时间复杂度 Θ(nn)

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;
}

回忆

upi 表示以 i 为结尾的研究中最浅的 si 的深度,这条链是必须要满足覆盖的

ai 表示 i 的子树中已经完成配对的链数(起始终止点都在子树中),bi 表示仍然时上下型链的数量,子树合并时可以贪心使得 bi 最少

子树中若有多条链的起始点浅于子树的根,保留最浅的一个作为子树根新的 up,余者在起点深度处打标记,回溯时将子树内造成的标记数量加入子树的 bi;若儿子节点的 up 是自身那么也作为儿子的 bi 中的一个

子树内其他点的 up 都深于子树根,那么可以贪心地将 bi 减一,没有则拆开一条已经配对的 ai

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;
}

posted @   没学完四大礼包不改名  阅读(89)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示