• 【考试总结】20220606


    排列

    设原来排列的置换环长度为 \(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;
    }
    

  • 相关阅读:
    ASP.NET Cookies简单应用 记住用户名和密码
    index.dat文件剖析
    簇集索引与聚集索引
    C#开发飞信机器人
    详解Javascript中的Url编码/解码
    基于关系型数据库的WEB OA公文流转系统
    今天Apple陆家嘴点“开战”
    准备在cnblogs活动上的演讲
    4年技术经验
    chinajoy之行
  • 原文地址:https://www.cnblogs.com/yspm/p/AHOI2022.html
Copyright © 2020-2023  润新知