【考试总结】2022-05-19

调兵遣将

先二分 +ST 表求 GCD 得到每个点为左/右端点的区间 GCD 不同的 Θ(nlogn) 个区间

枚举可能的 GCD 并求出来 fi 表示前 i 个元素最后一个选的军团位置 i 的方案数,gi 表示倒序处理中第一个军团起始点 i 的方案数

那么不经过某个点 i 的方案数就是 fi1×gi+1 ,乍一看仍然需要 Θ(n2logn) 的复杂度

但是不难发现 fi,gi 的值的结构是一段一段相同的,所以把这些区间的左右端点离散化下来区间加法即可

不离散化也过了

Code Display
const int N=50010;
int w[N],n;
map<int,vector<tuple<int,int,int> > > dir,inv;
int pre[N],c[N],suf[N],spre[N],ssuf[N];
int ans[N];
int bz[17][N],lg[N];
inline int get_gcd(int l,int r){
    int t=lg[r-l+1];
    return __gcd(bz[t][l],bz[t][r-(1<<t)+1]);
}
signed main(){
    n=read(); 
    rep(i,1,n) w[i]=read(),bz[0][i]=w[i];
    for(int j=1;j<=16;++j){
        for(int i=1;i+(1<<j)-1<=n;++i){
            bz[j][i]=__gcd(bz[j-1][i],bz[j-1][i+(1<<(j-1))]);
        }
    }
    rep(i,2,n) lg[i]=lg[i>>1]+1;
       
    for(int i=1;i<=n;++i){
        int pos=i;
        while(pos<=n){
            int val=get_gcd(i,pos),ans=pos,l=pos+1,r=n;
            while(l<=r){
                int mid=(l+r)>>1;
                if(get_gcd(i,mid)==val) ans=mid,l=mid+1;
                else r=mid-1;
            }
            dir[val].emplace_back(i,pos,ans);
            pos=ans+1;
        }
    }
    for(int i=n;i>=1;--i){
        int pos=i;
        while(pos>=1){
            int val=get_gcd(pos,i),ans=pos,l=1,r=pos-1;
            while(l<=r){
                int mid=(l+r)>>1;
                if(get_gcd(mid,i)==val) ans=mid,r=mid-1;
                else l=mid+1;
            }
            inv[val].emplace_back(i,ans,pos);
            pos=ans-1;
        }
    }
	int all=0;
    for(auto eee:dir){
        int gcd=eee.fir;
        vector<tuple<int,int,int> > Z=eee.sec,F=inv[gcd];
        pre[0]=suf[n+1]=spre[0]=ssuf[n+1]=1;
        rep(i,1,n) pre[i]=suf[i]=spre[i]=ssuf[i]=0;
        pre[1]=mod-1; suf[n]=mod-1;
        int indic=0;
        int a,b,c;
        tie(a,b,c)=Z[indic];
        for(int i=1;i<=n;++i){
            if(a==i){
                ckadd(pre[b],spre[i-1]);
                ckdel(pre[c+1],spre[i-1]);
                ++indic;
                if(indic<Z.size()) tie(a,b,c)=Z[indic];   
            }
            ckadd(pre[i],pre[i-1]);
            spre[i]=add(spre[i-1],pre[i]);
        } 
        indic=0;
        tie(a,b,c)=F[indic];
        for(int i=n;i>=1;--i){
            if(a==i){
                ckadd(suf[c],ssuf[i+1]);
                ckdel(suf[b-1],ssuf[i+1]);
                ++indic;
                if(indic<F.size()) tie(a,b,c)=F[indic];
            }
            ckadd(suf[i],suf[i+1]);
            ssuf[i]=add(ssuf[i+1],suf[i]);
        }
		ckadd(all,spre[n]);
		for(int i=1;i<=n;++i) ckadd(ans[i],mul(spre[i-1],ssuf[i+1]));
    }
	rep(i,1,n) print(del(all,ans[i]));
    putchar('\n');
    return 0;
}

一掷千金

注意到原题中给出的让黑点变成白点和让一个白点变成黑点在异或意义下效果是一样的,所以可以将每个每个唯一白点的局面的 SG 函数值求出再异或得到全局 SG

发现单个白点构成局面的情况的 SG 值只和 (i,j)(1,1) 之间的切比雪夫距离有关,也就是说考虑最外侧这一条链上的值即可

先抛出结论 SG((i,j))=lowbit(max(i,j)),证明分成 [0,lowbit(max(i,j))) 都存在于可达集合中以及 lowbit(max(i,j)) 不在可达集合里面,可达集合本质上是 {x|d[1,i),x=j=1dlowbit(ij)}

考察 d[1,lowbit(i)),j=1dlowbit(ij) 的结果发现其就是 log2lowbit(i) 位格雷码反射至十进制数得到的结果,对此证明不展开,简单考察进位变化即可

对于 lowbit(i) 不存在于可达集合的证明,发现根据格雷码的构造会在 j=lowbit(i)j=2lowbit(i)1 反向走一遍 j=1j=lowbit(i)1 的序列,这是 lowbit(i) 异或一个非零的 k ,在 j=2lowbit(i) 处前缀异或和是 0j=2lowbit(i)+1j=i1 的讨论不再赘述

要求若干矩形并的白点 SG 值的 之和可以对 [0,m] 这维开线段树维护扫描线

对于一个被矩形覆盖的区间那么 lowbit 的异或和是 lowbit(l)lowbit(mid),也就是说更小的位两两抵消掉了;而没有被覆盖的区间的 和可以使用子区间得到

那么在线段树上额外维护出来每个区间被覆盖点的个数应对两维取 max 操作即可

Code Display
const int N=1e5+10,U=(1ll<<30)-1;
int rt,tot,sum[N<<6],cnt[N<<6],cov[N<<6];
int K,n,m;
int ls[N<<6],rs[N<<6];
vector<tuple<int,int,int> >squ[N];
inline void push_up(int p){
    sum[p]=sum[ls[p]]^sum[rs[p]];
    cnt[p]=cnt[ls[p]]^cnt[rs[p]];
}
inline int calc(int L,int R){
    return (L&(-L))^((R-L+1)>>1);
}
inline void upd(int &p,int l,int r,int st,int ed,int d){
    if(!p) p=++tot;
    if(st<=l&&r<=ed){
        cov[p]+=d;
        if(cov[p]) sum[p]=calc(l,r),cnt[p]=(r-l+1)&1;
        else push_up(p);
        return ;
    }
    int mid=(l+r)>>1;
    if(st<=mid) upd(ls[p],l,mid,st,ed,d);
    if(ed>mid) upd(rs[p],mid+1,r,st,ed,d);
    if(!cov[p]) push_up(p);
}
inline int query_sum(int p,int l,int r,int st,int ed,int tag){
    tag|=cov[p];
    if(st<=l&&r<=ed){
        if(tag) return calc(l,r);
        else return sum[p];
    }
    int mid=(l+r)>>1,res=0;
    if(st<=mid) res^=query_sum(ls[p],l,mid,st,ed,tag);
    if(ed>mid) res^=query_sum(rs[p],mid+1,r,st,ed,tag);
    return res;
}
inline int query_cnt(int p,int l,int r,int st,int ed){
    if(!p) return 0;
    if(cov[p]) return (min(ed,r)-max(st,l)+1)&1;
    if(st<=l&&r<=ed) return cnt[p];
    int mid=(l+r)>>1,par=0;
    if(st<=mid) par^=query_cnt(ls[p],l,mid,st,ed);
    if(ed>mid) par^=query_cnt(rs[p],mid+1,r,st,ed);
    return par;
}
signed main(){
    K=read(); n=read(); m=read();
    for(int i=1;i<=K;++i){
        int sx=read(),sy=read(),ex=read(),ey=read();
        squ[sx].emplace_back(sy,ey,1);
        squ[ex+1].emplace_back(sy,ey,-1);
    }
    int ans=0;
    for(int i=1;i<=n;++i){
        for(auto s:squ[i]){
            int l,r,fl;
            tie(l,r,fl)=s;
            upd(rt,0,U,l,r,fl);
        }
        ans^=query_sum(rt,0,U,i+1,U,0);
        if(query_cnt(rt,0,U,0,i)&1) ans^=i&(-i);
    }
    print(ans);
    return 0;
}

树拓扑序

计算 fu,v 表示 (u,v) 两个点的子树合并后逆序对的平均数,枚举整个序列是 u 在末位还是 v 在末位

直接的贡献是 gequ,v/leqv,u 表示在 u/v 的子树中大于等于 v/u 的数的数量,剩下的问题是原问题的子问题,递归求解即可

和 2022-01-19 心理阴影一题是重题

Code Display
const int N=5010;
int ans,n,fa[N],siz[N],inv[N];
vector<int> g[N],sub[N];
int geq[N][N],C[N][N],all=1;
inline void dfs(int x,int fat){
    fa[x]=fat; sub[x].pb(x);
    rep(i,1,x) geq[x][i]++;
    for(int i:g[x]) if(i!=fat){
        dfs(i,x);
		siz[x]+=siz[i];
        rep(j,1,n) geq[x][j]+=geq[i][j];
        for(auto t:sub[i]) sub[x].pb(t),ans+=x<t;
		ckmul(all,C[siz[x]][siz[i]]);
	}
    siz[x]++;
	return ;
}
int f[N][N];
inline int calc_f(int u,int v){
    if(~f[v][u]) return f[v][u];
    if(~f[u][v]) return f[u][v]; 
	f[u][v]=add(mul(siz[u],geq[v][u]),mul(siz[v],geq[u][v]));
    for(auto t:g[u]) if(t^fa[u]){
        ckadd(f[u][v],mul(siz[u],calc_f(t,v)));
    }
    for(auto t:g[v]) if(t^fa[v]){
        ckadd(f[u][v],mul(siz[v],calc_f(t,u)));
    }
    return f[u][v]=mul(f[u][v],inv[siz[u]+siz[v]]);
}
inline void get_ans(int x){
    for(auto t:g[x]) if(t!=fa[x]) get_ans(t);
    int siz=g[x].size();
    rep(i,0,siz-1) if(g[x][i]^fa[x]) rep(j,i+1,siz-1) if(g[x][j]^fa[x]) ckadd(ans,calc_f(g[x][i],g[x][j]));
    return ;
}
signed main(){
    n=read();
    inv[0]=inv[1]=1;
    for(int i=2;i<=n;++i) inv[i]=mod-mul(mod/i,inv[mod%i])%mod;
    C[0][0]=1;
	rep(i,1,n){
		C[i][0]=1;
		for(int j=1;j<=i;++j) C[i][j]=add(C[i-1][j],C[i-1][j-1]);
	}
	rep(i,1,n-1){
        int u=read(),v=read();
        g[u].pb(v); g[v].pb(u);
    } 
	dfs(1,0);
    memset(f,-1,sizeof(f));
    get_ans(1);
    print(mul(ans,all));
    return 0;
}

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