Loading

多校省选联测22,24

多校省选联测24

拱火

发现一条边两侧点的数目都为奇数时这条边产生 $ 1 $ 的贡献

为了保证合法我们每次加入两个点 一共需要跑两次

对每一条边维护一颗线段树

如果对于一条边来说 第r次加入的两个点在边两侧 该叶子的值就为1

那么该边产生的所有贡献就是所有子区间的异或和 即$ \sum_{l=1}^n \sum_{r=l}^n \oplus_{i=l}^{r} a_i $

考虑这个东西可以用扫描线搞 每次第 $ r $ 次加入时计算每一个区间右端点为 $ r $ 的区间的贡献

此时复杂度为 $ O(n^2\log n) $

考虑优化 线段树此时维护的是后缀异或和 那么如果我们加入一个0 贡献不变 第 $ r $ 次加入一个1时 贡献从 $ sum $ 变为 $ (r-1)-sum+1=r-sum $

每次加入两个点时 只有两点之间路径上的点(去掉LCA)会加入1 树剖维护区间加 区间取负操作即可

总结:扫描线+贡献分析

code
#pragma GCC optimize("Ofast")
#include <iostream>
#include <vector>
#define Sakura int
#define Re register ll
#define _ putchar(' ')
#define el putchar('\n')
#define ll long long
#define fre(x,y) freopen(#x".in","r",stdin),freopen(#y".out","w",stdout);

using namespace std;

const ll maxn=5e5+10;

inline ll read(){
    ll x=0,f=0;char c=getchar();
    while(!isdigit(c)) f|=c=='-',c=getchar();
    while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar();
    return f?-x:x;
}

inline void ot(ll x) {
    if(x<0) putchar('-'),x=-x;
    if(x>9) ot(x/10);putchar(x%10|48);
}

vector<ll> G[maxn];
ll n;
ll dfn[maxn],dep[maxn],top[maxn],fa[maxn],size[maxn],son[maxn],tim;
ll ans;

struct Seg {
    ll sum[maxn<<2];
    ll add_tag[maxn<<2];
    bool rev_tag[maxn<<2];

    void empty(ll p,ll l,ll r) {
        sum[p]=add_tag[p]=rev_tag[p]=0;
        if(l==r) return;
        ll mid=l+r>>1;
        empty(p<<1,l,mid);
        empty(p<<1|1,mid+1,r);
    }

    inline void reverse(ll p) {
        rev_tag[p]^=1;
        sum[p]*=-1;
        add_tag[p]*=-1;
    }

    inline void add(ll p,ll len,ll x) {
        sum[p]+=x*len;
        add_tag[p]+=x;
    }

    inline void pushup(ll p) {
        sum[p]=sum[p<<1]+sum[p<<1|1];
    }

    inline void pushdown(ll p,ll l,ll r) {
        if(rev_tag[p]) {
            reverse(p<<1);
            reverse(p<<1|1);
            rev_tag[p]=false;
        }
        if(add_tag[p]) {
            ll mid=l+r>>1;
            add(p<<1,mid-l+1,add_tag[p]);
            add(p<<1|1,r-mid,add_tag[p]);
            add_tag[p]=0;
        }
    }

    void rev(ll p,ll l,ll r,ll L,ll R) {
        if(L>R) return;
        if(L<=l&&r<=R) return reverse(p);
        pushdown(p,l,r);
        ll mid=l+r>>1;
        if(L<=mid) rev(p<<1,l,mid,L,R);
        if(mid<R) rev(p<<1|1,mid+1,r,L,R);
        pushup(p);
    }

    void upd(ll p,ll l,ll r,ll L,ll R,ll x) {
        if(L>R) return;
        if(L<=l&&r<=R) return add(p,r-l+1,x);
        pushdown(p,l,r);
        ll mid=l+r>>1;
        if(L<=mid) upd(p<<1,l,mid,L,R,x);
        if(mid<R) upd(p<<1|1,mid+1,r,L,R,x);
        pushup(p);
    }
}tree;

inline void add(ll x,ll y) {
    G[x].emplace_back(y);
}

inline void add_edge(ll x,ll y) {
    add(x,y);
    add(y,x);
}

void dfs1(ll u) {
    dep[u]=dep[fa[u]]+1;
    size[u]=1;
    for(auto v:G[u])
        if(v!=fa[u]) {
            fa[v]=u;
            dfs1(v);
            if(size[v]>size[son[u]]) son[u]=v;
            size[u]+=size[v];
        }
}

void dfs2(ll u,ll topf) {
    top[u]=topf;
    dfn[u]=++tim;
    if(son[u]) dfs2(son[u],topf);
    for(auto v:G[u]) if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}

inline void upd(ll x,ll y,ll z) {
    // ot(x),_,ot(y),_,ot(z),el;
    while(top[x]!=top[y]) {
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        tree.rev(1,1,n,dfn[top[x]],dfn[x]);
        tree.upd(1,1,n,dfn[top[x]],dfn[x],z);
        x=fa[top[x]];
    }
    if(dep[x]<dep[y]) swap(x,y);
    tree.rev(1,1,n,dfn[y]+1,dfn[x]);
    tree.upd(1,1,n,dfn[y]+1,dfn[x],z);
}

inline void solve(ll st) {
    for(Re i=st,d=1;i<n;i+=2,d++) {
        upd(i,i+1,d);
        ans+=tree.sum[1];
    }
}

Sakura main() {
    fre(fire,fire);
    n=read();
    for(Re i=1;i<n;++i) add_edge(read(),read());
    dfs1(1);
    dfs2(1,1);
    solve(1);
    tree.empty(1,1,n);
    solve(2);
    ot(ans);
}

卷积之王

sto Kaguya orz

考虑 $ max $ 这个操作似乎不可以 $ DFT $ (反正我不会

考虑按最高位是 $ 0 $ 还是 $ 1 \quad $ 将两个序列分别拆为 $ A,B $ 和 $ C,D \quad $ $ A,C $ 最高位为 $ 1 $

显然去掉最高位 $ A,B \quad $ $ C,D $ 分别同构

那么卷积出来最高位为 $ 0 $ 的部分显然只能由 $ B * D $ 得到

最高位为 $ 1 $ 的可以由 $ A * C ,A * D, C * B $ 得到

由于同构 发现 $ \max( A * C ,A * D ) = A * \max( C ,D) $

这样上述三次计算可以合并成两次

复杂度:$ T(n) = 3T ( \frac{n}{2} ) + O(n) \quad $ 即 $ O(3^k) $

总结:同构类型合并计算 卷积矩阵幂次等

code
#pragma GCC optimize("Ofast")
#include <iostream>
#define Sakura int
#define Re register int
#define _ putchar(' ')
#define el putchar('\n')
#define fre(x,y) freopen(#x".in","r",stdin),freopen(#y".out","w",stdout);

using namespace std;

const int maxn=1<<17;

inline int read(){
	int x=0,f=0;char c=getchar();
	while(!isdigit(c)) f|=c=='-',c=getchar();
	while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return f?-x:x;
}

inline void ot(int x) {
	if(x<0) putchar('-'),x=-x;
	if(x>9) ot(x/10);putchar(x%10|48);
}

int n,N;
int a[maxn],b[maxn],c[maxn];
int d[maxn],top;

void solve(int *a,int *b,int *c,int n) {
	if(n<=32) {
		for(Re i=0;i<n;++i)
			for(Re j=0;j<n;++j)
				c[i|j]=max(c[i|j],a[i]+b[j]);
		return;
	}
	solve(a,b,c,n>>1);
	int *sav=d+top;
	top+=n>>1;
	for(Re i=0;i<(n>>1);++i) sav[i]=a[i],a[i]=max(a[i],a[i+(n>>1)]);
	solve(a,b+(n>>1),c+(n>>1),n>>1);
	for(Re i=0;i<(n>>1);++i) a[i]=sav[i];
	for(Re i=0;i<(n>>1);++i) sav[i]=b[i],b[i]=max(b[i],b[i+(n>>1)]);
	solve(a+(n>>1),b,c+(n>>1),n>>1);
	for(Re i=0;i<(n>>1);++i) b[i]=sav[i];
	top-=n>>1;
}

Sakura main() {
	fre(ztl,ztl);
	n=read();
	N=1<<n;
	for(Re i=0;i<N;++i) a[i]=read();
	for(Re i=0;i<N;++i) b[i]=read();
	solve(a,b,c,N);
	for(Re i=0;i<N;++i) ot(c[i]),_;
}

多校省选联测22

因懒无名

撤销不好搞 直接线段树分治

题目要求变成了求一段区间内的集合并的树的直径

一个我几百年前就忘了的结论:两棵树的直径端点分别为 $ A,B $ 和 $ C,D $ 时 两棵树点集的并的产生的树的直径端点肯定还是 $ A,B,C,D $ 之二

线段树直接维护即可

求lca时用st表 复杂度从 $ O(n\log^3n) $ 变为 $ O(n\log^2n) $

总结:树的直径结论 去删除操作

code
#pragma GCC optimize("Ofast")
#include <iostream>
#include <vector>
#include <cmath>
#define Sakura int
#define Re register ll
#define _ putchar(' ')
#define el putchar('\n')
#define ll long long
#define fst first
#define scd second
#define fre(x,y) freopen(#x".in","r",stdin),freopen(#y".out","w",stdout);

using namespace std;

const ll maxn=1e5+10;
const ll inf=1e9;

inline ll read(){
    ll x=0,f=0;char c=getchar();
    while(!isdigit(c)) f|=c=='-',c=getchar();
    while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar();
    return f?-x:x;
}

inline void ot(ll x) {
    if(x<0) putchar('-'),x=-x;
    if(x>9) ot(x/10);putchar(x%10|48);
}

int n,m,q;
int a[maxn];
int tim[maxn];
int opt[maxn],lq[maxn],rq[maxn];

struct Graph {
    vector<int> G[maxn];
    int dep[maxn],dfn[maxn],id[maxn],tim;
    int Min[maxn<<1][19],cnt;

    inline void add(int x,int y) {
        G[x].emplace_back(y);
    }

    inline void add_edge(int x,int y) {
        add(x,y);
        add(y,x);
    }

    void dfs(int u,int f) {
        dep[u]=dep[f]+1;
        Min[++cnt][0]=++tim;
        dfn[u]=cnt;
        id[tim]=u;
        for(auto v:G[u]) 
            if(v!=f) {
                dfs(v,u);
                Min[++cnt][0]=Min[dfn[u]][0];
            }
    }

    inline int Lca(int x,int y) {
        int l=dfn[x],r=dfn[y];
        if(l>r) swap(l,r);
        int k=log2(r-l+1);
        return id[min(Min[l][k],Min[r-(1<<k)+1][k])];
    }

    inline int get_dis(int x,int y) {
        return dep[x]+dep[y]-dep[Lca(x,y)]*2;
    }

    inline void init() {
        for(Re i=1;i<n;++i) add_edge(read(),read());
        dfs(1,0);
        for(Re j=1;j<=18;++j)
            for(Re i=1;i+(1<<j)-1<=cnt;++i)
                Min[i][j]=min(Min[i][j-1],Min[i+(1<<j-1)][j-1]);
    }
}G;

struct node {
    int x,y,dis;

    inline bool friend operator < (const node &A,const node &B) {
        return A.dis<B.dis;
    }

    inline friend node operator + (const node &A,const node &B) {
        if(!A.x) return B;
        if(!B.x) return A;
        node C=A<B?B:A;
        int dis=G.get_dis(A.x,B.x);
        if(dis>C.dis) C={A.x,B.x,dis};

        dis=G.get_dis(A.x,B.y);
        if(dis>C.dis) C={A.x,B.y,dis};

        dis=G.get_dis(A.y,B.x);
        if(dis>C.dis) C={A.y,B.x,dis};

        dis=G.get_dis(A.y,B.y);
        if(dis>C.dis) C={A.y,B.y,dis};
        return C;
    }

    inline void out() { ot(x),_,ot(y),_,ot(dis),el; }
};

int top;
pair<int,node> stk[maxn];

struct Seg {
    node nd[maxn<<2];

    inline void pushup(int p) {
        nd[p]=nd[p<<1]+nd[p<<1|1];
    }

    void upd(int p,int l,int r,int pos,int x) {
        if(l==r) {
            nd[p]=nd[p]+node{x,x,0};
            // ot(l),_,nd[p].out();
            return;
        }
        int mid=l+r>>1;
        pos<=mid?upd(p<<1,l,mid,pos,x):upd(p<<1|1,mid+1,r,pos,x);
        pushup(p);
    }

    void modify(int p,int l,int r,int pos,node x) {
        if(l==r) {
            nd[p]=x;
            return;
        }
        int mid=l+r>>1;
        pos<=mid?modify(p<<1,l,mid,pos,x):modify(p<<1|1,mid+1,r,pos,x);
        pushup(p);
    }

    node qry(int p,int l,int r,int L,int R) {
        // _,ot(l),_,ot(r),_,ot(L),_,ot(R),_,nd[p].out();
        if(L<=l&&r<=R) return nd[p];
        int mid=l+r>>1;
        if(R<=mid) return qry(p<<1,l,mid,L,R);
        if(mid<L) return qry(p<<1|1,mid+1,r,L,R);
        return qry(p<<1,l,mid,L,R)+qry(p<<1|1,mid+1,r,L,R);
    }
}Tree;

struct seg {
    vector<pair<int,int> > vec[maxn<<2];

    void upd(int p,int l,int r,int L,int R,int x,int col) {
        // if(p==1) ot(x),_,ot(col),_,ot(L),_,ot(R),el;
        if(L>R) return;
        if(L<=l&&r<=R) return vec[p].emplace_back(x,col);
        int mid=l+r>>1;
        if(L<=mid) upd(p<<1,l,mid,L,R,x,col);
        if(mid<R) upd(p<<1|1,mid+1,r,L,R,x,col);
    }

    inline void push(int x,int col) {
        stk[++top]={col,Tree.qry(1,1,m,col,col)};
        Tree.upd(1,1,m,col,x);
    }

    inline void pop() {
        Tree.modify(1,1,m,stk[top].fst,stk[top].scd);
        top--;
    }

    void solve(int p,int l,int r) {
        int last_top=top;
        for(Re i=0;i<vec[p].size();++i) push(vec[p][i].fst,vec[p][i].scd);
        if(l==r) {
            if(opt[l]==2) ot(Tree.qry(1,1,m,lq[l],rq[l]).dis),el;
            // if(l==r) _,Tree.nd[1].out();
            while(top!=last_top) pop();
            return;
        }
        int mid=l+r>>1;
        solve(p<<1,l,mid);
        solve(p<<1|1,mid+1,r);
        while(top!=last_top) pop();
    }
}tree;

Sakura main() {
    fre(noname,noname);
    n=read(),m=read(),q=read();
    for(Re i=1;i<=n;++i) a[i]=read();
    G.init();
    for(Re i=1;i<=n;++i) tim[i]=1;
    for(Re i=1;i<=q;++i) {
        opt[i]=read();
        int x=read(),y=read();
        if(opt[i]==1) {
            tree.upd(1,1,q,tim[x],i-1,x,a[x]);
            tim[x]=i;
            a[x]=y;
        }
        else lq[i]=x,rq[i]=y;
    }
    for(Re i=1;i<=n;++i) tree.upd(1,1,q,tim[i],q,i,a[i]);
    tree.solve(1,1,q);
}

树论

sto Kaguya orz

发现一个点的 $ mex $ 值就是其子树内的最大深度 而整棵树的 $ SG $ 值就是每个点的 $ mex $ 值的异或和

因为该游戏类似树上nim游戏

当该节点有奇数个节点时 产生贡献

到此刻除了换根所有操作都可以用维护翻转的树剖做了

至于换根 我们考虑%Kaguya

当我们默认以1为根时 只有目前root到1的点贡献会改变

如果链上的儿子是答案来源 那么我们需要用次大值作为贡献

那么那些点需要转换贡献呢?不知道

所以请出来了长链剖分

因为此时重儿子一定是答案来源 所以只有链末的点不用以次大值作为答案 也就是每一个 $ fa[top[x]] $ 和root

预处理最大值次大值 长链剖分建立两颗线段树支持区间异或和翻转和求区间异或值即可

复杂度 $ O(n\sqrt{n}\log{n}) $

总结:多看看博弈论

code
#pragma GCC optimize("Ofast")
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
#define Sakura int
#define Re register int
#define _ putchar(' ')
#define el putchar('\n')
#define fst first
#define scd second
#define fre(x,y) freopen(#x".in","r",stdin),freopen(#y".out","w",stdout);

using namespace std;

const int maxn=2e5+10;
const int inf=1e9;

inline int read(){
	int x=0,f=0;char c=getchar();
	while(!isdigit(c)) f|=c=='-',c=getchar();
	while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return f?-x:x;
}

inline void ot(int x) {
	if(x<0) putchar('-'),x=-x;
	if(x>9) ot(x/10);putchar(x%10|48);
}

vector<int> G[maxn];
vector<int> vec[maxn];
int n,m;
int fa[maxn],dep[maxn],size[maxn],dfn[maxn],len[maxn][2],son[maxn],top[maxn],tim;
int root=1;


struct Seg {
	int a[maxn];
	int sum1[maxn<<2],sum2[maxn<<2];
	bool lazy[maxn<<2];

	inline void change(int p) {
		lazy[p]^=1;
		sum2[p]^=sum1[p];
	}

	inline void pushup(int p) {
		sum2[p]=sum2[p<<1]^sum2[p<<1|1];
	}

	inline void pushdown(int p) {
		if(!lazy[p]) return;
		change(p<<1);
		change(p<<1|1);
		lazy[p]=false;
	}

	void build(int p,int l,int r) {
		if(l==r) {
			sum1[p]=sum2[p]=a[l];
			return;
		}
		int mid=l+r>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		sum1[p]=sum1[p<<1]^sum1[p<<1|1];
		pushup(p);
	}

	void upd(int p,int l,int r,int L,int R) {
		if(L>R) return;
		if(L<=l&&r<=R) return change(p);
		pushdown(p);
		int mid=l+r>>1;
		if(L<=mid) upd(p<<1,l,mid,L,R);
		if(mid<R) upd(p<<1|1,mid+1,r,L,R);
		pushup(p);
	}

	int qry(int p,int l,int r,int L,int R) {
		if(L>R) return 0;
		if(L<=l&&r<=R) return sum2[p];
		pushdown(p);
		int mid=l+r>>1;
		if(R<=mid) return qry(p<<1,l,mid,L,R);
		if(mid<L) return qry(p<<1|1,mid+1,r,L,R);
		return qry(p<<1,l,mid,L,R)^qry(p<<1|1,mid+1,r,L,R);
	}
}tree[2];

inline void add(int x,int y) {
	G[x].emplace_back(y);
}

inline void add_edge(int x,int y) {
	add(x,y);
	add(y,x);
}

void dfs1(int u,int f) {
	fa[u]=f;
	dep[u]=dep[f]+1;
	size[u]=1;
	for(auto v:G[u]) 
		if(v!=f) {
			dfs1(v,u);
			size[u]+=size[v];
			len[u][0]=max(len[u][0],len[v][0]+1);
			if(len[v][0]>len[son[u]][0]) son[u]=v;
		}
}

void dfs2(int u,int topf,int l) {
	int res=++l;
	for(auto v:G[u]) if(v!=fa[u]&&v!=son[u]) res=max(res,len[v][0]+1);
	len[u][1]=res;
	top[u]=topf;
	dfn[u]=++tim;
	if(son[u]) {
		vec[u].emplace_back(tim+1);
		dfs2(son[u],topf,res);
	}
	res=max(l,len[u][0]);
	for(auto v:G[u]) 
		if(v!=fa[u]&&v!=son[u]) {
			vec[u].emplace_back(tim+1);
			dfs2(v,v,res);
		}
	vec[u].emplace_back(tim+1);
}

inline void qry(int x) {
	root=x;
	int ans=tree[0].qry(1,1,n,1,n);
	while(x) {
		ans^=tree[0].qry(1,1,n,dfn[top[x]],dfn[x]);
		ans^=tree[1].qry(1,1,n,dfn[top[x]],dfn[x]-1);
		ans^=max(tree[0].qry(1,1,n,dfn[x],dfn[x]),tree[1].qry(1,1,n,dfn[x],dfn[x]));
		x=fa[top[x]];
	}
	ot(ans),el;
}

Sakura main() {
	fre(tree,tree);
	n=read(),m=read();
	for(Re i=1;i<n;++i) add_edge(read(),read());
	len[0][0]=-1;
	dfs1(1,0);
	dfs2(1,1,-1);
	// for(Re i=1;i<=n;++i) ot(dfn[i]),_;el;
	// for(Re i=1;i<=n;++i) ot(len[i][0]),_;el;
	// for(Re i=1;i<=n;++i) ot(len[i][1]),_;el;
	// for(Re i=1;i<=n;++i) ot(top[i]),_;el;
	// for(Re i=1;i<=n;++i) ot(son[i]),_;el;
	// for(Re i=1;i<=n;++i) ot(size[i]),_;el;
	for(Re i=1;i<=n;++i) 
		for(Re j=0;j<2;++j)
			tree[j].a[dfn[i]]=len[i][j];
	for(Re i=0;i<2;++i) tree[i].build(1,1,n);
	while(m--) {
		int opt=read(),x=read(),y;
		if(opt==1) y=read();
		int rt=read();
		if(opt==1) { 
			while(top[x]!=top[y]) {
				if(dep[top[x]]<dep[top[y]]) swap(x,y);
				for(Re i=0;i<2;++i) tree[i].upd(1,1,n,dfn[top[x]],dfn[x]);
				x=fa[top[x]];
			}
			if(dep[x]<dep[y]) swap(x,y);
			for(Re i=0;i<2;++i) tree[i].upd(1,1,n,dfn[y],dfn[x]);
			qry(rt);
		}
		else {
			if(x==root) for(Re i=0;i<2;++i) tree[i].upd(1,1,n,1,n);
			else if(dfn[x]<=dfn[root]&&dfn[root]+size[root]-1<=dfn[x]+size[x]-1) {
				y=upper_bound(vec[x].begin(),vec[x].end(),dfn[root])-vec[x].begin();
				for(Re i=0;i<2;++i) tree[i].upd(1,1,n,1,n),tree[i].upd(1,1,n,vec[x][y-1],vec[x][y]-1);
			}
			else for(Re i=0;i<2;++i) tree[i].upd(1,1,n,dfn[x],dfn[x]+size[x]-1);
			qry(rt);
		}
	}
}

游戏

时停停时定理 详见这篇boke

CF1349D Slime and Biscuits 的改编题 基本没改编

posted @ 2023-02-03 20:52  hzoi_Sakura  阅读(38)  评论(2编辑  收藏  举报