多校省选联测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 的改编题 基本没改编