CWOI DS 专题 1
A - 相逢是问候
考虑拓展欧拉定理,那么在 \(\mathcal{O}(\log p)\) 层的幂塔后指数就会变为常数,所以暴力在线段树上更新即可,复杂度 \(\mathcal{O}(n\log p)\)。似乎需要光速幂,细节有点多。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e4+5,M=(1<<15)-1,D=60;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int getphi(int n){
int res=n;
for(int i=2;i*i<=n;i++){
if(n%i==0){
res=res/i*(i-1);
while(n%i==0)n/=i;
}
}
if(n>1)res=res/n*(n-1);
return res;
}
int eul(int n,int p){
return (n>=p)?(n%p+p):n;
}
int n,m,c,p,cntp,phi[D],c1[D][M+5],c2[D][M+5];
void init(){
cntp=0,phi[cntp]=p;
while(phi[cntp]>1)cntp++,phi[cntp]=getphi(phi[cntp-1]);
for(int i=0;i<=cntp;i++){
c1[i][0]=c2[i][0]=1;
for(int j=1;j<=M;j++)c1[i][j]=eul(c1[i][j-1]*c,phi[i]);
c2[i][1]=eul(c1[i][M]*c,phi[i]);
for(int j=2;j<=M;j++)c2[i][j]=eul(c2[i][j-1]*c2[i][1],phi[i]);
}
}
int cpow(int n,int i){
return eul(c1[i][n&M]*c2[i][n>>15],phi[i]);
}
int calc(int x,int cnt,int i){
if(!cnt)return eul(x,phi[i]);
if(i==cntp)return c?1:0;
return cpow(calc(x,cnt-1,i+1),i);
}
int a[N][D];
struct segtree{
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int s,tag;
}c[N<<2];
void pushup(int rt){
c[rt].s=(c[ls].s+c[rs].s)%p;
c[rt].tag=min(c[ls].tag,c[rs].tag);
}
void build(int l,int r,int rt){
c[rt].tag=0;
if(l==r){
c[rt].s=a[l][0];
return;
}
int mid=(l+r)>>1;build(lson),build(rson);
pushup(rt);
}
void update(int l,int r,int rt,int L,int R){
if(c[rt].tag>cntp)return;
if(l==r){
c[rt].tag++,c[rt].s=a[l][c[rt].tag];
return;
}
int mid=(l+r)>>1;
if(L<=mid)update(lson,L,R);
if(R>mid)update(rson,L,R);
pushup(rt);
}
int query(int l,int r,int rt,int L,int R){
if(L<=l&&r<=R)return c[rt].s;
int mid=(l+r)>>1,res=0;
if(L<=mid)res+=query(lson,L,R),res%=p;
if(R>mid)res+=query(rson,L,R),res%=p;
return res;
}
#undef ls
#undef rs
#undef lson
#undef rson
}Tr;
signed main(){
n=read(),m=read(),p=read(),c=read();init();
for(int i=1;i<=n;i++){
a[i][0]=read();
for(int j=1;j<=cntp+1;j++)a[i][j]=calc(a[i][0],j,0)%p;
a[i][0]%=p;
}
Tr.build(1,n,1);
while(m--){
int op=read();
if(op==0){
int l=read(),r=read();
Tr.update(1,n,1,l,r);
}
if(op==1){
int l=read(),r=read();
printf("%lld\n",Tr.query(1,n,1,l,r)%p);
}
}
return 0;
}
B - 楼房重建
兔队线段树模板题。
首先问题等价于单点改,查询 \(\dfrac{H_i}{i}\) 有几个严格前缀最大值。考虑兔队线段树。具体地,线段树维护 \(s(l,r)\) 表示 \([l,r]\) 有几个严格前缀最大值,\(m(l,r)\) 表示 \([l,r]\) 的最大值。当合并两个区间 \([l,mid]\),\((mid,r]\) 的时候,令 \(c(l,r,k)\) 表示在 \([l,r]\) 中有多少个严格前缀最大值大于 \(k\),那么 \(s(l,r)=s(l,mid)+c(mid+1,r,m(l,mid))\)。现在问题转化为了如何快速维护 \(c\),发现这可以直接利用线段树的信息来求解。
具体地,若到达叶子可以直接比较;若 \(m(l,mid)\ge k\),说明 \((mid,r]\) 内的所有前缀最大值都大于 \(k\),有 \(c(l,r,k)=c(l,mid,k)+s(l,r)-s(l,mid)\);否则说明左区间不会影响答案,\(c(l,r,k)=c(mid+1,r,k)\)。因为每次只会递归一侧,复杂度是 \(\mathcal{O}(n\log^2n)\) 的。
进一步的,考虑区间信息没有可减性的情况。修改 \(s(l,r)\) 的定义:\(s(l,r)\) 表示考虑 \([l,r]\),在 \((mid,r]\) 内有几个严格前缀最大值。可以类似的得到 \(s(l,r)=c(mid+1,r,m(l,mid))\),当 \(m(l,mid)\ge k\) 有 \(c(l,r,k)=c(l,mid,k)+s(l,r)\),反之 \(c(l,r,k)=c(mid+1,r,k)\)。调用 \(c(1,n,0)\) 即为答案。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define db double
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int a[100005];db v[100005];
struct segtree{
#define ls p<<1
#define rs p<<1|1
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int s;db ma;
}c[400005];
int ask(int l,int r,int p,db k){
if(l==r)return (c[p].ma>k);
int mid=(l+r)>>1;
if(c[ls].ma<k)return ask(rson,k);
else return ask(lson,k)+c[p].s;
}
void pushup(int l,int r,int p){
c[p].ma=max(c[ls].ma,c[rs].ma);
int mid=(l+r)>>1;c[p].s=ask(rson,c[ls].ma);
}
void build(int l,int r,int p){
if(l==r){c[p].s=0,c[p].ma=0;return;}
int mid=(l+r)>>1;
build(lson),build(rson);
pushup(l,r,p);
}
void update(int l,int r,int p,int x,int k){
if(l==r){a[l]=k,v[l]=k*1.0/l,c[p].s=0,c[p].ma=v[l];return;}
int mid=(l+r)>>1;
if(x<=mid)update(lson,x,k);
else update(rson,x,k);
pushup(l,r,p);
}
#undef ls
#undef rs
#undef lson
#undef rson
}Tr;
signed main(){
int n=read(),m=read();Tr.build(1,n,1);
while(m--){
int x=read(),y=read();Tr.update(1,n,1,x,y);
printf("%lld\n",Tr.ask(1,n,1,0.0));
}
return 0;
}
C - 二分图 /【模板】线段树分治
把每条边出现的时间段分成 \(\log k\) 段,丢到每个线段树上每个区间对应的 vector 里,然后 dfs。考虑并查集判断二分图,每次从父亲进入儿子的时候加上儿子的边,退出时撤销,可以用可撤销并查集维护,复杂度 \(\mathcal{O}(n\log^2n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int fa[200005],h[200005];stack<pii>s;
int find(int x){
return ((x==fa[x])?x:find(fa[x]));
}
void merge(int x,int y){
if(x==y)return;
if(h[x]<h[y])swap(x,y);
s.push(mk(y,(h[x]==h[y]))),fa[y]=x,h[x]+=(h[x]==h[y]);
}
vector<pii>vec[400005];int n,m,k,ans[100005];
void insert(int l,int r,int p,int L,int R,int x,int y){
if(L<=l&&r<=R){vec[p].push_back(mk(x,y));return;}
int mid=(l+r)>>1;
if(L<=mid)insert(l,mid,p<<1,L,R,x,y);
if(R>mid)insert(mid+1,r,p<<1|1,L,R,x,y);
}
void recyc(int top){
while((int)s.size()>top){
int u=s.top().fi,c=s.top().se;s.pop();
h[fa[u]]-=c,fa[u]=u;
}
}
void solve(int l,int r,int p){
int mid=(l+r)>>1,oldtop=(int)s.size();
for(int i=0;i<(int)vec[p].size();i++){
int u=vec[p][i].fi,v=vec[p][i].se;
int fu=find(u),fv=find(v);
if(fu==fv){
for(int j=l;j<=r;j++)ans[j]=0;
recyc(oldtop);
return;
}
merge(find(u+n),fv),merge(fu,find(v+n));
}
if(l!=r)solve(l,mid,p<<1),solve(mid+1,r,p<<1|1);
else ans[l]=1;
recyc(oldtop);
}
signed main(){
n=read(),m=read(),k=read();
for(int i=1;i<=n+n;i++)fa[i]=i,h[i]=0;
for(int i=1;i<=m;i++){
int x=read(),y=read(),l=read()+1,r=read()+1;
if(l!=r)insert(1,k,1,l,r-1,x,y);
}
solve(1,k,1);
for(int i=1;i<=k;i++)puts(ans[i]?"Yes":"No");
return 0;
}
D - 岛屿探险
是我不会的题。
看到 \(\min(b,d)\) 第一时间想到分类讨论。当 \(b>d\) 时条件是 \(a\oplus c\le d\),这个显然可以丢到 trie 上维护。但是 \(b\le d\) 时条件变为 \(a\oplus c\le b\),不好做。
发现这个式子具有高度对称性,考虑把询问挂到 trie 上。具体地,我们在插入一对 \((a,b)\) 时,如果 \(b\) 当前位为 1,说明前面与 \(a\oplus b\) 相同,这一位为 0 的这些 \(c\) 合法,这就是一个子树加,打个标记。
最后还需要把 \(b>d\) 和 \(b\le d\) 的情况分离开。考虑把点和询问丢到一起按 \(b\) 或 \(d\) 从小到大排序,直接 CDQ 就完了。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct Trie1{//solver for b<=d
int T=1,son[13500005][2],tag[13500005];
int ch(int p,int o){
if(!son[p][o])son[p][o]=++T;
return son[p][o];
}
void clear(){
for(int i=1;i<=T;i++)son[i][0]=son[i][1]=tag[i]=0;
T=1;
}
void insert(int a,int b){
int u=1;
for(int i=23;i>=0;i--){
int oa=(a>>i)&1ll,ob=(b>>i)&1ll;
if(ob)tag[ch(u,oa)]++;
u=ch(u,oa^ob);
}
tag[u]++;
}
int ask(int c){
int u=1,res=0;
for(int i=23;i>=0;i--){
int oc=(c>>i)&1ll;
res+=tag[u],u=ch(u,oc);
}
res+=tag[u];
return res;
}
}Tr1;
int root[100005];
struct Trie2{//solver for b>d
int T,son[13500005][2],cnt[13500005];
void clear(){
for(int i=1;i<=T;i++)son[i][0]=son[i][1]=cnt[i]=0;
T=0;
}
void insert(int p,int q,int x){
for(int i=23;i>=0;i--){
int o=(x>>i)&1ll;
if(!son[p][o])son[p][o]=++T;
son[p][o^1]=son[q][o^1];
cnt[p]=cnt[q]+1,p=son[p][o],q=son[q][o];
}
cnt[p]=cnt[q]+1;
}
int ask(int p,int a,int b){
int res=0;
for(int i=23;i>=0;i--){
int oa=(a>>i)&1ll,ob=(b>>i)&1ll;
if(ob==0)p=son[p][oa];
else res+=cnt[son[p][oa]],p=son[p][oa^1];
}
res+=cnt[p];
return res;
}
}Tr2;
struct Que{
int id,pos,a,b,op,val;
}q[300005],p[300005];
int cmp1(Que x,Que y){
if(x.b^y.b)return x.b<y.b;
return x.op>y.op;
}
int cmp2(Que x,Que y){
if(x.pos^y.pos)return x.pos<y.pos;
return x.op<y.op;
}
int tot,B[100005],ans[100005];
void solve(int l,int r){
if(l==r)return;
int mid=(l+r)>>1,cnt=0;solve(l,mid);solve(mid+1,r);
cnt=0;Tr1.clear();//b<=d
for(int i=l;i<=mid;i++)if(q[i].op==1)p[++cnt]=q[i];
for(int i=mid+1;i<=r;i++)if(q[i].op==2)p[++cnt]=q[i];
sort(p+1,p+cnt+1,cmp2);
for(int i=1;i<=cnt;i++){
if(p[i].op==1)Tr1.insert(p[i].a,p[i].b);
else ans[p[i].id]+=p[i].val*Tr1.ask(p[i].a);
}
cnt=tot=0;Tr2.clear();//b>d
for(int i=mid+1;i<=r;i++)if(q[i].op==1)B[++tot]=q[i].pos,p[++cnt]=q[i];
sort(p+1,p+cnt+1,cmp2);sort(B+1,B+tot+1);
for(int i=1;i<=cnt;i++)root[i]=++Tr2.T,Tr2.insert(root[i],root[i-1],p[i].a);
for(int i=l;i<=mid;i++){
if(q[i].op==1)continue;
int pos=upper_bound(B+1,B+tot+1,q[i].pos)-B-1;
if(pos<1||pos>tot||B[pos]>q[i].pos)continue;
ans[q[i].id]+=q[i].val*Tr2.ask(root[pos],q[i].a,q[i].b);
}
}
signed main(){
int n=read(),m=read(),qt=0;
for(int i=1,a,b;i<=n;i++)a=read(),b=read(),q[++qt]=(Que){i,i,a,b,1,0};
for(int i=1,l,r,c,d;i<=m;i++)l=read(),r=read(),c=read(),d=read(),q[++qt]=(Que){i,l-1,c,d,2,-1},q[++qt]=(Que){i,r,c,d,2,1};
sort(q+1,q+qt+1,cmp1);solve(1,qt);
for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
return 0;
}
E - 归程
kruskal 重构树。
把所有边按海拔从大到小排序,然后依次加边。每次加 \((u,v,l,a)\) 的时候新建一个点权为 \(a\) 的节点,然后把 \(u,v\) 所在连通块的根与新点连边。那么在原图中 \(u\to v\) 路径上海拔最小的点就是新图上 \(\text{lca}(u,v)\) 的点权。对于每个询问,我们可以从 \(v\) 往上跳到最后一个点权大于 \(p\) 的点 \(u\),那么 \(u\) 子树内所有点是可以互相到达的,只需要求出这些点中到 1 最近的距离,这个可以 dijkstra 预处理,找 \(u\) 可以倍增。数组不要开小。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<ll,int>pii;
const ll inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,w,nxt;
}e[1600005];
int tot,head[400005];
void add(int u,int v,int w){
e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
int n,m,cur,q,k,s,vis[200005];ll lastans,d[400005];
void dijkstra(int s){
for(int i=1;i<=n;i++)d[i]=inf,vis[i]=0;
priority_queue<pii,vector<pii>,greater<pii> >q;
d[s]=0;q.push(mk(d[s],s));
while(!q.empty()){
int u=q.top().se;q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(d[v]>d[u]+w)d[v]=d[u]+w,q.push(mk(d[v],v));
}
}
}
int pa[400005][25],dep[400005];
void dfs(int u,int fa){
dep[u]=dep[fa]+1,pa[u][0]=fa;if(u>n)d[u]=inf;
for(int i=0;i<=22;i++)pa[u][i+1]=pa[pa[u][i]][i];
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs(v,u);d[u]=min(d[u],d[v]);
}
}
int getlca(int x,int y){
if(dep[x]>dep[y])swap(x,y);
for(int i=22;i>=0&&dep[x]!=dep[y];i--)if(dep[y]-dep[x]>=(1<<i))y=pa[y][i];
if(x==y)return x;
for(int i=22;i>=0;i--)if(pa[x][i]!=pa[y][i])x=pa[x][i],y=pa[y][i];
return pa[x][0];
}
int p[400005];
int jump(int u,int v){
for(int i=22;i>=0;i--)if(pa[u][i]&&p[pa[u][i]]>v)u=pa[u][i];
return u;
}
struct Node{
int u,v,l,a;
}g[400005];
int cmp(Node x,Node y){
return x.a>y.a;
}
int fa[400005];
int find(int x){
return ((x==fa[x])?x:fa[x]=find(fa[x]));
}
void solve(){
n=read(),m=read(),lastans=0,cur=n;
for(int i=1;i<=m;i++){
g[i].u=read(),g[i].v=read(),g[i].l=read(),g[i].a=read();
add(g[i].u,g[i].v,g[i].l),add(g[i].v,g[i].u,g[i].l);
}
sort(g+1,g+m+1,cmp);dijkstra(1);
tot=0;for(int i=1;i<=2*n;i++)head[i]=0,fa[i]=i,p[i]=0;
for(int i=1;i<=m;i++){
int u=g[i].u,v=g[i].v,a=g[i].a,fu=find(u),fv=find(v);
if(fu!=fv)p[++cur]=a,add(cur,fu,1),add(fu,cur,1),add(cur,fv,1),add(fv,cur,1),fa[fu]=fa[fv]=cur;
}
dfs(cur,0);
q=read(),k=read(),s=read();
while(q--){
int v=(read()+1ll*k*lastans%n-1)%n+1,p=(read()+1ll*k*lastans%(s+1))%(s+1);
v=jump(v,p);printf("%lld\n",(lastans=d[v]));
}
tot=0;for(int i=1;i<=2*n;i++)head[i]=0;
}
signed main(){
int T=read();
while(T--){
solve();
}
return 0;
}
F - 可持久化并查集
按秩合并,可持久化父亲和高度/大小。
感觉还是维护高度好一点吧,虽然大小也是对的。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,ver,_[200005];
struct segtree{
#define ls c[p].lc
#define rs c[p].rc
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int c,lc,rc;
}c[7000005];
int T,root[200005];
int build(int l,int r){
int p=++T;
if(l==r){c[p].c=_[l];return p;}
int mid=(l+r)>>1;
ls=build(l,mid),rs=build(mid+1,r);
return p;
}
int update(int l,int r,int q,int x,int k){
int p=++T;c[p]=c[q];
if(l==r){c[p].c=k;return p;}
int mid=(l+r)>>1;
if(x<=mid)ls=update(l,mid,c[q].lc,x,k);
else rs=update(mid+1,r,c[q].rc,x,k);
return p;
}
int query(int l,int r,int p,int x){
if(l==r)return c[p].c;
int mid=(l+r)>>1;
if(x<=mid)return query(lson,x);
else return query(rson,x);
}
#undef ls
#undef rs
#undef lson
#undef rson
}TrFa,TrSiz;
int find(int x){
while(1){
int fa=TrFa.query(1,n,TrFa.root[ver],x);
if(fa!=x)x=fa;else break;
}
return x;
}
void merge(int x,int y){
int a=find(x),b=find(y);if(a==b)return;
int sza=TrSiz.query(1,n,TrSiz.root[ver],a),szb=TrSiz.query(1,n,TrSiz.root[ver],b);
if(sza>szb)swap(x,y),swap(a,b),swap(sza,szb);
TrFa.root[ver]=TrFa.update(1,n,TrFa.root[ver],a,b);
TrSiz.root[ver]=TrSiz.update(1,n,TrSiz.root[ver],b,sza+szb);
}
void recall(int k){
TrFa.root[ver]=TrFa.root[k];
TrSiz.root[ver]=TrSiz.root[k];
}
int ask(int x,int y){
int a=find(x),b=find(y);
return (a==b);
}
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++)_[i]=i;
TrFa.root[0]=TrFa.build(1,n);
for(int i=1;i<=n;i++)_[i]=1;
TrSiz.root[0]=TrSiz.build(1,n);
for(ver=1;ver<=m;ver++){
TrFa.root[ver]=TrFa.root[ver-1];
TrSiz.root[ver]=TrSiz.root[ver-1];
int op=read(),a,b;
if(op==1)a=read(),b=read(),merge(a,b);
else if(op==2)a=read(),recall(a);
else a=read(),b=read(),printf("%d\n",ask(a,b));
}
return 0;
}
G - 整数序列
考虑根号分治。记一个阈值 \(S\),当 \(cnt_x\) 和 \(cnt_y\) 都小于 \(S\) 的时候可以跑 \(\mathcal{O}(\sum(cnt_x+cnt_y))\) 的暴力;当 \(cnt_x\) 和 \(cnt_y\) 都不小于 \(S\) 时这样的 \(x,y\) 不超过 \(\dfrac{n}{S}\) 个,令这些数为 \(p_1\ldots p_m\),极限情况有 \(\sum\limits_{i=1}^mcnt_i=n\),两两配对后复杂度为 \(\sum\limits_{i=1}^m\sum\limits_{j=i+1}^mcnt_i+cnt_j<m\sum\limits_{i=1}^mcnt_i=mn\),故这部分复杂度是 \(\mathcal{O}(\dfrac{n^2}{S})\)。
考虑一大一小的情况。令 \(cnt_x<S,cnt_y\ge S\)。容易发现并不是所有的为 \(y\) 的位置都有可能成为端点,具体的,对于一段连续 \(l\) 个 x,向左向右各找 \(l+1\) 个 y,找过的跳过,那么这些 y 就是可能成为端点的位置,然后跑暴力,复杂度 \(\mathcal{O}(\sum cnt_x\log cnt_y)\)。
为什么要找 \(l+1\) 个 y 捏?因为你现在找的左端点其实是原来的左端点 -1 啊。
调试的过程有点久,主要是因为在家里没什么动力写题。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,q,T,a[300005],b[300005],id[300005],siz[300005],pos[600005],B[600005];
vector<int>v[300005],sum[300005];set<int>s[300005];
map<int,int>vis[300005];
signed main(){
n=read(),q=read(),T=500;
for(int i=1;i<=n;i++)a[i]=read(),v[a[i]].push_back(i),id[i]=(int)v[a[i]].size()-1;
for(int i=1;i<=n;i++)b[i]=read();
for(int i=1;i<=n;i++){
siz[i]=(int)v[i].size();sum[i].resize(siz[i]+5);
if(!siz[i])continue;
for(int j=0;j<siz[i];j++)s[i].insert(v[i][j]);
sum[i][0]=b[v[i][0]];for(int j=1;j<siz[i];j++)sum[i][j]=sum[i][j-1]+b[v[i][j]];
}
for(int i=0;i<=n+n;i++)B[i]=inf;
while(q--){
int x=read(),y=read(),ans=-inf;
if(vis[x].count(y)){printf("%lld\n",vis[x][y]);continue;}
if((siz[x]<=T&&siz[y]<=T)||(siz[x]>T&&siz[y]>T)){
int cx=0,cy=0,tot=0;
while(cx<siz[x]&&cy<siz[y]){
if(v[x][cx]<v[y][cy])pos[++tot]=v[x][cx],cx++;
else pos[++tot]=v[y][cy],cy++;
}
while(cx<siz[x])pos[++tot]=v[x][cx],cx++;
while(cy<siz[y])pos[++tot]=v[y][cy],cy++;
int cnt=0,val=0;B[cnt+siz[y]]=val;
for(int i=1;i<=tot;i++){
cnt+=((a[pos[i]]==x)?1:-1),val+=b[pos[i]];
ans=max(ans,val-B[cnt+siz[y]]);
B[cnt+siz[y]]=min(B[cnt+siz[y]],val);
}
cnt=0,val=0;B[cnt+siz[y]]=inf;
for(int i=1;i<=tot;i++){
cnt+=((a[pos[i]]==x)?1:-1),val+=b[pos[i]];
B[cnt+siz[y]]=inf;
}
}
else{
if(siz[x]>siz[y])swap(x,y);
int tot=0;
for(int i=0;i<siz[x];i++){
if(s[y].empty())break;
auto it=s[y].lower_bound(v[x][i]);
if(it==s[y].begin())continue;
it=prev(it),pos[++tot]=*it;
if(it==s[y].begin()){s[y].erase(it);continue;}
auto tmp=prev(it);s[y].erase(it),it=tmp,pos[++tot]=*it,s[y].erase(it);
}
for(int i=0;i<siz[x];i++){
if(s[y].empty())break;
auto it=s[y].lower_bound(v[x][i]);
if(it==s[y].end())continue;
pos[++tot]=*it;
auto tmp=next(it);s[y].erase(it),it=tmp;
if(it==s[y].end())continue;
pos[++tot]=*it,s[y].erase(it);
}
for(int i=1;i<=tot;i++)s[y].insert(pos[i]);
for(int i=0;i<siz[x];i++)pos[++tot]=v[x][i];
sort(pos+1,pos+tot+1);
int cnt=0,val=0,lstx=-1,lsty=-1;B[cnt+siz[y]]=val;
for(int i=1;i<=tot;i++){
if(a[pos[i]]==x)cnt+=id[pos[i]]-lstx,val+=sum[x][id[pos[i]]]-((lstx==-1)?0:sum[x][lstx]),lstx=id[pos[i]];
else cnt-=id[pos[i]]-lsty,val+=sum[y][id[pos[i]]]-((lsty==-1)?0:sum[y][lsty]),lsty=id[pos[i]];
ans=max(ans,val-B[cnt+siz[y]]);
B[cnt+siz[y]]=min(B[cnt+siz[y]],val);
}
cnt=0,lstx=-1,lsty=-1;B[cnt+siz[y]]=inf;
for(int i=1;i<=tot;i++){
if(a[pos[i]]==x)cnt+=id[pos[i]]-lstx,lstx=id[pos[i]];
else cnt-=id[pos[i]]-lsty,lsty=id[pos[i]];
B[cnt+siz[y]]=inf;
}
}
printf("%lld\n",(vis[x][y]=vis[y][x]=ans));
}
return 0;
}
H - Count on a tree II/【模板】树分块
考虑在树上每隔 \(S\) 个点就撒一个关键点,预处理关键点间的颜色情况,存到 bitset 里。时间复杂度 \(\mathcal{O}(n\log n+n+\dfrac{n^3}{S^2w})\),空间复杂度 \(\mathcal{O}(\dfrac{n^3}{S^2w})\)。
查询就是令 \(t=\text{lca}(u,v)\),先从 \(u,v\) 暴力跳到一个关键点,然后跳到 \(t\) 之下最后一个关键点,然后再暴力跳到 \(t\) 即可。时间复杂度 \(\mathcal{O}(m(\log n+S+\dfrac{n}{S}+\dfrac{n}{w}))\)。
当 \(S\) 取 \(\sqrt{n}\) 时有最优时间复杂度 \(\mathcal{O}((n+m)\log n+m\sqrt{n}+\dfrac{nm+n^2}{w})\),可以适当调大 \(S\) 来获得更优的空间复杂度。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[80005];
int tot,head[40005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int n,m,lastans,siz,cur,tag[40005],a[40005],rnk[40005],pa[40005][20],dep[40005];
void dfs1(int u,int fa,int dis){
if(dis==siz)tag[u]=++cur,rnk[cur]=u,dis=0;
dep[u]=dep[fa]+1,pa[u][0]=fa;
for(int i=0;i<=16;i++)pa[u][i+1]=pa[pa[u][i]][i];
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs1(v,u,dis+1);
}
}
int top,s[40005],pre[40005];bitset<40005>B[55][55],tmp;
void dfs2(int u,int fa){
if(tag[u]){
tmp.reset();tmp.set(a[u]);B[tag[u]][tag[u]].set(a[u]);
int p=pa[u][0];while(p&&!tag[p])tmp.set(a[p]),p=pa[p][0];
for(int i=1;i<=top;i++)B[s[i]][tag[u]]=B[s[i]][s[top]]|tmp;
pre[u]=rnk[s[top]],s[++top]=tag[u];
}
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs2(v,u);
}
if(tag[u])top--;
}
int getlca(int x,int y){
if(dep[x]>dep[y])swap(x,y);
for(int i=16;i>=0&&dep[x]!=dep[y];i--)if(dep[y]-dep[x]>=(1<<i))y=pa[y][i];
if(x==y)return x;
for(int i=16;i>=0;i--)if(pa[x][i]!=pa[y][i])x=pa[x][i],y=pa[y][i];
return pa[x][0];
}
int cnt,b[40005];
signed main(){
n=read(),m=read(),lastans=0,siz=1000;
for(int i=1;i<=n;i++)a[i]=b[++cnt]=read();
sort(b+1,b+cnt+1);cnt=unique(b+1,b+cnt+1)-b-1;
for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+cnt+1,a[i])-b;
for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
dfs1(1,0,siz);dfs2(1,0);
while(m--){
int u=read()^lastans,v=read(),lca=getlca(u,v);tmp.reset();
while(!tag[u]&&u!=lca)tmp.set(a[u]),u=pa[u][0];
while(!tag[v]&&v!=lca)tmp.set(a[v]),v=pa[v][0];
int tu=u,tv=v;
while(pre[u]&&dep[pre[u]]>=dep[lca])u=pre[u];
while(pre[v]&&dep[pre[v]]>=dep[lca])v=pre[v];
tmp|=B[tag[u]][tag[tu]]|B[tag[v]][tag[tv]];
while(u&&u!=lca)tmp.set(a[u]),u=pa[u][0];
while(v&&v!=lca)tmp.set(a[v]),v=pa[v][0];
tmp.set(a[lca]);
printf("%d\n",(lastans=tmp.count()));
}
return 0;
}
I - 二逼平衡树(树套树)
开一颗线段树,每个线段树节点开一颗平衡树,不想写。
考虑序列分块套值域分块。定义 \(f_{i,j}\) 表示前 \(i\) 个块值落在值域上第 \(j\) 个块的数的个数,\(g_{i,j}\) 表示前 \(i\) 个块落在值域上 \(j\) 这个点的个数。实现略麻烦,但思路清晰,复杂度 \(\mathcal{O}(n\sqrt{n})\),需要离散化。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,INF=2147483647;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,nsiz,nnum,nbel[50005],nL[505],nR[505];
int tot,tsiz,tnum,tbel[100005],tL[505],tR[505];
int m,a[50005],b[100005],f[505][505],g[505][100005],cntf[505],cntg[100005];
struct Que{
int op,l,r,v;
}q[50005];
int getrank(int l,int r,int v){
int res=0;
if(nbel[r]-nbel[l]<=1){
for(int i=l;i<=r;i++)res+=(a[i]<v);
return res+1;
}
for(int i=l;i<=nR[nbel[l]];i++)res+=(a[i]<v);
for(int i=1;i<tbel[v];i++)res+=f[nbel[r]-1][i]-f[nbel[l]][i];
for(int i=tL[tbel[v]];i<v;i++)res+=g[nbel[r]-1][i]-g[nbel[l]][i];
for(int i=nL[nbel[r]];i<=r;i++)res+=(a[i]<v);
return res+1;
}
void add(int l,int r,int v){
for(int i=l;i<=nR[nbel[l]];i++)cntf[tbel[a[i]]]+=v,cntg[a[i]]+=v;
for(int i=nL[nbel[r]];i<=r;i++)cntf[tbel[a[i]]]+=v,cntg[a[i]]+=v;
}
int getval(int l,int r,int v){
int sum=0;add(l,r,1);
for(int i=1;i<=tnum;i++){
if(f[nbel[r]-1][i]-f[nbel[l]][i]+cntf[i]&&sum+f[nbel[r]-1][i]-f[nbel[l]][i]+cntf[i]+1>v){
for(int j=tL[i];j<=tR[i];j++){
if(g[nbel[r]-1][j]-g[nbel[l]][j]+cntg[j]&&sum+g[nbel[r]-1][j]-g[nbel[l]][j]+cntg[j]+1>v)return add(l,r,-1),b[j];
sum+=g[nbel[r]-1][j]-g[nbel[l]][j]+cntg[j];
}
return add(l,r,-1),-INF;
}
sum+=f[nbel[r]-1][i]-f[nbel[l]][i]+cntf[i];
}
return add(l,r,-1),-INF;
}
void update(int p,int v){
for(int i=nbel[p];i<=nnum;i++)f[i][tbel[a[p]]]--,g[i][a[p]]--;
a[p]=v;
for(int i=nbel[p];i<=nnum;i++)f[i][tbel[a[p]]]++,g[i][a[p]]++;
}
int getpre(int l,int r,int v){
add(l,r,1);
for(int i=v-1;i>=tL[tbel[v]];i--){
if(g[nbel[r]-1][i]-g[nbel[l]][i]+cntg[i])return add(l,r,-1),b[i];
}
for(int i=tbel[v]-1;i>=1;i--){
if(f[nbel[r]-1][i]-f[nbel[l]][i]+cntf[i]){
for(int j=tR[i];j>=tL[i];j--){
if(g[nbel[r]-1][j]-g[nbel[l]][j]+cntg[j])return add(l,r,-1),b[j];
}
return add(l,r,-1),-INF;
}
}
return add(l,r,-1),-INF;
}
int getsuf(int l,int r,int v){
add(l,r,1);
for(int i=v+1;i<=tR[tbel[v]];i++){
if(g[nbel[r]-1][i]-g[nbel[l]][i]+cntg[i])return add(l,r,-1),b[i];
}
for(int i=tbel[v]+1;i<=tnum;i++){
if(f[nbel[r]-1][i]-f[nbel[l]][i]+cntf[i]){
for(int j=tL[i];j<=tR[i];j++){
if(g[nbel[r]-1][j]-g[nbel[l]][j]+cntg[j])return add(l,r,-1),b[j];
}
return add(l,r,-1),INF;
}
}
return add(l,r,-1),INF;
}
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=b[++tot]=read();
for(int i=1;i<=m;i++){
int op=read(),l,r,v;
if(op==3)l=read(),v=read(),q[i]=(Que){op,l,l,v};
else l=read(),r=read(),v=read(),q[i]=(Que){op,l,r,v};
if(op!=2)b[++tot]=q[i].v;
}
sort(b+1,b+tot+1);tot=unique(b+1,b+tot+1)-b-1;
for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
for(int i=1;i<=m;i++)if(q[i].op!=2)q[i].v=lower_bound(b+1,b+tot+1,q[i].v)-b;
nsiz=(int)sqrt(n),nnum=(n+nsiz-1)/nsiz;
tsiz=(int)sqrt(tot),tnum=(tot+tsiz-1)/tsiz;
for(int i=1;i<=n;i++)nbel[i]=(i-1)/nsiz+1;
for(int i=1;i<=tot;i++)tbel[i]=(i-1)/tsiz+1;
for(int i=1;i<=nnum;i++)nL[i]=(i-1)*nsiz+1,nR[i]=i*nsiz;
for(int i=1;i<=tnum;i++)tL[i]=(i-1)*tsiz+1,tR[i]=i*tsiz;
nR[nnum]=n,tR[tnum]=tot;
for(int i=1;i<=nnum;i++){
for(int j=1;j<=tnum;j++)f[i][j]=f[i-1][j];
for(int j=1;j<=tot;j++)g[i][j]=g[i-1][j];
for(int j=nL[i];j<=nR[i];j++)f[i][tbel[a[j]]]++,g[i][a[j]]++;
}
for(int i=1;i<=m;i++){
int op=q[i].op,l=q[i].l,r=q[i].r,v=q[i].v;
if(op==1)printf("%lld\n",getrank(l,r,v));
else if(op==2)printf("%lld\n",getval(l,r,v));
else if(op==3)update(l,v);
else if(op==4)printf("%lld\n",getpre(l,r,v));
else printf("%lld\n",getsuf(l,r,v));
}
return 0;
}
J - 维护数列
平衡树板子。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int inf=1e9,P=1145141;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int a[500005];
mt19937 rnd(19260817);
struct TangGeneral{
#define ls(p) (c[p].lc)
#define rs(p) (c[p].rc)
#define val(p) (c[p].val)
#define pri(p) (c[p].pri)
#define siz(p) (c[p].siz)
#define sum(p) (c[p].sum)
#define lseg(p) (c[p].lseg)
#define rseg(p) (c[p].rseg)
#define seg(p) (c[p].seg)
#define cov(p) (c[p].cov)
#define rev(p) (c[p].rev)
struct Node{
int lc,rc,val,pri,siz,sum,lseg,rseg,seg,cov,rev;
}c[500005];
int T,Root;stack<int>s;
void build(){
T=0;while(!s.empty())s.pop();
}
int newnode(int val){
int u,seed=rnd()%P;
if(s.empty())u=++T;
else u=s.top(),s.pop();
c[u]=(Node){0,0,val,seed,1,val,val,val,val,-inf,0};
return u;
}
void pushup(int p){
siz(p)=siz(ls(p))+siz(rs(p))+1;
sum(p)=sum(ls(p))+sum(rs(p))+val(p);
lseg(p)=max(lseg(ls(p)),sum(ls(p))+val(p)+max(0,lseg(rs(p))));
rseg(p)=max(rseg(rs(p)),max(0,rseg(ls(p)))+val(p)+sum(rs(p)));
seg(p)=max({(ls(p)?seg(ls(p)):-inf),(rs(p)?seg(rs(p)):-inf),max(0,rseg(ls(p)))+val(p)+max(0,lseg(rs(p)))});
}
void pushdown(int p){
if(cov(p)!=-inf){
val(ls(p))=cov(p),val(rs(p))=cov(p);
sum(ls(p))=siz(ls(p))*cov(p),sum(rs(p))=siz(rs(p))*cov(p);
lseg(ls(p))=rseg(ls(p))=seg(ls(p))=(cov(p)>=0?siz(ls(p)):1)*cov(p);
lseg(rs(p))=rseg(rs(p))=seg(rs(p))=(cov(p)>=0?siz(rs(p)):1)*cov(p);
cov(ls(p))=cov(p),cov(rs(p))=cov(p);
cov(p)=-inf;
}
if(rev(p)){
swap(lseg(ls(p)),rseg(ls(p))),swap(lseg(rs(p)),rseg(rs(p)));
swap(ls(ls(p)),rs(ls(p))),swap(ls(rs(p)),rs(rs(p)));
rev(ls(p))^=1;rev(rs(p))^=1;
rev(p)=0;
}
}
void splits(int p,int siz,int &x,int &y){
pushdown(p);if(!p){x=y=0;return;}
if(siz(ls(p))+1<=siz)x=p,splits(rs(p),siz-siz(ls(p))-1,rs(x),y);
else y=p,splits(ls(p),siz,x,ls(y));
pushup(p);
}
int merge(int x,int y){
if(!x||!y)return x+y;
if(pri(x)>pri(y)){
pushdown(x);rs(x)=merge(rs(x),y);
return pushup(x),x;
}
else{
pushdown(y);ls(y)=merge(x,ls(y));
return pushup(y),y;
}
}
void recyc(int p){
s.push(p);
if(ls(p))recyc(ls(p));
if(rs(p))recyc(rs(p));
}
void insert(int pos,int tot){
int x,y;splits(Root,pos,x,y);
for(int i=1;i<=tot;i++)x=merge(x,newnode(a[i]));
Root=merge(x,y);
}
void erase(int pos,int tot){
int x,y,z;splits(Root,pos-1,x,y);splits(y,tot,y,z);
recyc(y);Root=merge(x,z);
}
void cover(int pos,int tot,int col){
int x,y,z;splits(Root,pos-1,x,y);splits(y,tot,y,z);
val(y)=col,sum(y)=siz(y)*col,lseg(y)=rseg(y)=seg(y)=(col>=0?siz(y):1)*col,cov(y)=col;
Root=merge(merge(x,y),z);
}
void rever(int pos,int tot){
int x,y,z;splits(Root,pos-1,x,y);splits(y,tot,y,z);
swap(ls(y),rs(y));swap(lseg(y),rseg(y));rev(y)^=1;
Root=merge(merge(x,y),z);
}
int asksum(int pos,int tot){
int x,y,z;splits(Root,pos-1,x,y);splits(y,tot,y,z);
int res=sum(y);Root=merge(merge(x,y),z);
return res;
}
int askseg(){
return seg(Root);
}
#undef ls
#undef rs
#undef val
#undef pri
#undef siz
#undef sum
#undef lseg
#undef rseg
#undef seg
#undef cov
#undef rev
}Tr;
char op[25];
signed main(){
int n=read(),m=read();Tr.build();
for(int i=1;i<=n;i++)a[i]=read();
Tr.insert(0,n);
while(m--){
scanf("%s",op);
if(op[2]=='S'){
int pos=read(),tot=read();
for(int i=1;i<=tot;i++)a[i]=read();
Tr.insert(pos,tot);
}
else if(op[2]=='L'){
int pos=read(),tot=read();
Tr.erase(pos,tot);
}
else if(op[2]=='K'){
int pos=read(),tot=read(),col=read();
Tr.cover(pos,tot,col);
}
else if(op[2]=='V'){
int pos=read(),tot=read();
Tr.rever(pos,tot);
}
else if(op[2]=='T'){
int pos=read(),tot=read();
printf("%d\n",Tr.asksum(pos,tot));
}
else{
printf("%d\n",Tr.askseg());
}
}
return 0;
}
K - 密码箱
L - 宝石
M - The Tree
神奇 trick 题。考虑对询问分块,每次把涉及到的所有点拉出来建虚树,记录原图上两点间点个数和白色点个数。几个操作都可以暴力维护,因为只会涉及至多 \(\sqrt{q}\) 个点。修改完后根据虚树上两点间白点个数可以还原原图,二操作可以看打的标记,复杂度 \(\mathcal{O}((n+q)\sqrt{q})\)。
有别的做法,还不会。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{int v,w,d;};vector<int>g[100005];vector<edge>G[100005];
int n,q,siz,num,col[100005],cnt[100005],cov[100005],tag[100005];
void build(int u,int rt,int w,int d){
for(auto v:g[u]){
if(tag[v])G[rt].push_back((edge){v,w,d}),build(v,v,0,0);
else build(v,rt,w+(!col[v]),d+1);
}
}
void update(int u){
if(col[u]==0)return col[u]=1,void();
cnt[u]++;for(auto x:G[u])if(cnt[u]>x.w)update(x.v);
}
void cover(int u){
cnt[u]=col[u]=0,cov[u]=1;
for(auto &x:G[u])x.w=x.d,cover(x.v);
}
void rebuild(int u,int c,int t){
if(tag[u])c=cnt[u],t|=cov[u];
else{
if(t)col[u]=0;
if(!col[u]&&c)c--,col[u]=1;
}
for(auto v:g[u])rebuild(v,c,t);
}
int op[100005],p[100005];
signed main(){
n=read(),q=read(),siz=(int)sqrt(q),num=(q+siz-1)/siz;tag[1]=1;
for(int i=2;i<=n;i++)g[read()].push_back(i);
for(int i=1;i<=n;i++)col[i]=0;
for(int i=1;i<=q;i++)op[i]=read(),p[i]=read();
for(int i=1;i<=num;i++){
int l=(i-1)*siz+1,r=min(i*siz,q);
for(int j=1;j<=n;j++)G[j].clear(),cov[j]=cnt[j]=0;
for(int j=l;j<=r;j++)tag[p[j]]++;
build(1,1,0,0);
for(int j=l;j<=r;j++){
if(op[j]==1)update(p[j]);
else if(op[j]==2)cover(p[j]);
else puts(col[p[j]]?"black":"white");
}
rebuild(1,0,0);
for(int j=l;j<=r;j++)tag[p[j]]--;
}
return 0;
}
N - Bear and Bowling
O - 你的名字。
哎,卡常。
考虑根号分治。当 \(k\le T\) 时我们对每种可能的 \(k\) 预处理 \(a_i\bmod k\),然后分成 \(\sqrt{n}\) 块,每块块内维护前后缀最小值,对所有块再跑 ST 表。当询问两端点在同一块内时暴力查询,不在同一块内时分成整块和散块 \(\mathcal{O}(1)\) 查询,复杂度 \(\mathcal{O}(T(n+\sqrt{n}\log\sqrt{n})+\sum[k_i\le T])\);当 \(k>T\) 时发现 \(\dfrac{V}{k}\) 不会太大,可以枚举 \(i\),每次只考虑 \(a_j\in[ik,(i+1)k)\) 的位置,然后类似上述做法处理。
发现此时空间复杂度是 \(\mathcal{O}(\dfrac{mV}{T})\) 级别的,不太行。不妨转为枚举 \(ik\),此时不需要额外开空间。同时由于本题求最小值,我们只用保证 \(a_j\ge ik\) 即可,于是可以从大到小枚举,每次逐渐加入,单次修改复杂度为 \(\mathcal{O}(\sqrt{n}+(1+2+4+\ldots \sqrt{n}))=\mathcal{O}(\sqrt{n})\) 级别,总复杂度 \(\mathcal{O}(n\sqrt{n}+V\ln V+\sum[k_i>T])\)。
注意本题非常卡常,有几个细节需要注意:
-
适当调整块长,经试验取在 630 左右最优;
-
快读,以及其他所有常用、基本的卡常技巧,以及不要用 vector;
-
玄学的估价函数:可以大概预估一下两种方法需要的大概时间,然后考虑选哪种;
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
struct IO {
#define MAXSIZE (1 << 20)
#define isdigit(x) (x >= '0' && x <= '9')
char buf[MAXSIZE], *p1, *p2;
char pbuf[MAXSIZE], *pp;
#if DEBUG
#else
IO() : p1(buf), p2(buf), pp(pbuf) {}
~IO() { fwrite(pbuf, 1, pp - pbuf, stdout); }
#endif
char gc() {
#if DEBUG
return getchar();
#endif
if (p1 == p2) p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin);
return p1 == p2 ? ' ' : *p1++;
}
bool blank(char ch) {
return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
}
template <class T>
void read(T &x) {
double tmp = 1;
bool sign = 0;
x = 0;
char ch = gc();
for (; !isdigit(ch); ch = gc())
if (ch == '-') sign = 1;
for (; isdigit(ch); ch = gc()) x = x * 10 + (ch - '0');
if (ch == '.')
for (ch = gc(); isdigit(ch); ch = gc())
tmp /= 10.0, x += tmp * (ch - '0');
if (sign) x = -x;
}
void read(char *s) {
char ch = gc();
for (; blank(ch); ch = gc())
;
for (; !blank(ch); ch = gc()) *s++ = ch;
*s = 0;
}
void read(char &c) {
for (c = gc(); blank(c); c = gc())
;
}
void push(const char &c) {
#if DEBUG
putchar(c);
#else
if (pp - pbuf == MAXSIZE) fwrite(pbuf, 1, MAXSIZE, stdout), pp = pbuf;
*pp++ = c;
#endif
}
template <class T>
void write(T x) {
static T sta[35];
T top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
while (top) push(sta[--top] + '0');
}
template <class T>
void write(T x, char lastChar) {
write(x), push(lastChar);
}
} io;
struct Que{
int l,r,k,id;
}q[300005];
inline int cmpq(Que x,Que y){
return x.k<y.k;
}
int n,m,V,T,a[300005],b[300005],c[300005],ans[300005],lv[100005],rv[100005],lp[100005],rp[100005];
int siz,num,bel[300005],L[635],R[635],pr[635][635],sf[635][635],Log[635],f[12][635];
inline int qry(int l,int r){
int il=bel[l],ir=bel[r],o=Log[(ir-1)-(il+1)+1];
if(ir-il==1)return min(sf[il][l-L[il]+1],pr[ir][r-L[ir]+1]);
return min({sf[il][l-L[il]+1],pr[ir][r-L[ir]+1],f[o][(il+1)],f[o][(ir-1)-(1<<o)+1]});
}
inline void solve(int k){
for(int i=1;i<=n;++i)c[i]=a[i]%k;
for(int i=1;i<=num;++i){
pr[i][1]=c[L[i]];
for(int j=L[i]+1;j<=R[i];++j)pr[i][j-L[i]+1]=min(pr[i][j-L[i]],c[j]);
sf[i][R[i]-L[i]+1]=c[R[i]];
for(int j=R[i]-1;j>=L[i];--j)sf[i][j-L[i]+1]=min(sf[i][j-L[i]+2],c[j]);
}
for(int i=1;i<=num;++i)f[0][i]=pr[i][R[i]-L[i]+1];
for(int j=1;j<=Log[num];++j){
for(int i=1;i+(1<<j)-1<=num;i++){
f[j][i]=min(f[j-1][i],f[j-1][i+(1<<(j-1))]);
}
}
for(int x=lv[k];x<=rv[k];++x)if(q[x].r-q[x].l+1>siz)ans[q[x].id]=qry(q[x].l,q[x].r);
}
inline int cmpa(int x,int y){
return a[x]>a[y];
}
signed main(){
io.read(n),io.read(m),siz=630,num=(n+siz-1)/siz;
for(int i=1;i<=n;++i)bel[i]=(i-1)/siz+1;
for(int i=1;i<=num;++i)L[i]=(i-1)*siz+1,R[i]=i*siz;
R[num]=n;
Log[1]=0;for(int i=2;i<=num+2;++i)Log[i]=Log[i>>1]+1;
for(int i=1;i<=n;++i)io.read(a[i]),V=max(V,a[i]),b[i]=i;
for(int i=1;i<=m;++i)io.read(q[i].l),io.read(q[i].r),io.read(q[i].k),q[i].id=i,ans[i]=inf;
sort(q+1,q+m+1,cmpq);sort(b+1,b+n+1,cmpa);
for(int i=1;i<=n;++i)lp[a[b[i]]]=(lp[a[b[i]]]?lp[a[b[i]]]:i),rp[a[b[i]]]=i;
for(int i=1;i<=m;++i){
int l=q[i].l,r=q[i].r,k=q[i].k,id=q[i].id;T=max(T,k);
if(r-l+1<=siz){
for(int j=l;j<=r;++j)ans[id]=min(ans[id],a[j]%k);
continue;
}
lv[k]=(lv[k]?lv[k]:i),rv[k]=i;
}
int w=3*n+siz*Log[siz]-n*siz/m;
for(int i=1;i<=T;++i)if(w<2.7*(V/i-1)*(rv[i]-lv[i]+1))solve(i),lv[i]=0;
for(int i=1;i<=num;++i)for(int j=1;j<=R[i]-L[i]+1;++j)pr[i][j]=sf[i][j]=inf;
for(int j=0;j<=Log[num];++j)for(int i=1;i+(1<<j)-1<=num;++i)f[j][i]=inf;
for(int i=V;i>=1;i--){
for(int x=lp[i];x<=rp[i];++x){
int p=b[x],id=bel[p];
for(int i=p;i<=R[id];++i)pr[id][i-L[id]+1]=a[p];
for(int i=L[id];i<=p;++i)sf[id][i-L[id]+1]=a[p];
for(int j=0;j<=Log[num];++j){
int lt=max(1,id-(1<<j)+1),rt=min(id,num-(1<<j)+1);
for(int i=lt;i<=rt;++i)f[j][i]=a[p];
}
}
for(int j=1;j*j<=i;++j){
if(i%j)continue;
if(lv[j]){
for(int x=lv[j];x<=rv[j];++x){
int l=q[x].l,r=q[x].r,id=q[x].id;
if(r-l+1>siz)ans[id]=min(ans[id],qry(l,r)-i);
}
}
if(lv[i/j]&&i/j!=j){
for(int x=lv[i/j];x<=rv[i/j];++x){
int l=q[x].l,r=q[x].r,id=q[x].id;
if(r-l+1>siz)ans[id]=min(ans[id],qry(l,r)-i);
}
}
}
}
for(int i=1;i<=m;++i){
int l=q[i].l,r=q[i].r,id=q[i].id;
if(r-l+1>siz&&lv[q[i].k])ans[id]=min(ans[id],qry(l,r));
}
for(int i=1;i<=m;++i)io.write(ans[i],'\n');
return 0;
}
P - Breadboard Capacity (hard version)
Q - Fusion tree
考虑对每个点开 trie 维护答案。如果一个点记录自己周围所有点的信息,那么在菊花图上一次修改会导致所有节点都需要修改。考虑每个节点只记录自己所有儿子的信息,询问时单独判断父亲。这样一次修改只会影响一个点。
单点减和维护异或和平凡,考虑如何维护全局加一的操作。这是一个经典的 trick。不同于平时的 trie,这道题的 trie 是从低到高维护。容易发现全局加一就是找到最小的那个 0 把它变成 1,然把后面所有的 1 变成 0。对应在这颗 trie 上就是交换左右儿子,然后递归处理现在的 \(son_{u,0}\)(即原来的 \(son_{u,1}\))直到不存在 \(son_{u,0}\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[1000005];
int head[500005],tot;
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int T,root[500005],son[10000005][2],c[10000005],w[10000005];
void pushup(int p){
c[p]=0,w[p]=0;
if(son[p][0])c[p]^=c[son[p][0]],w[p]^=w[son[p][0]]<<1;
if(son[p][1])c[p]^=c[son[p][1]],w[p]^=(w[son[p][1]]<<1)|c[son[p][1]];
}
void ins(int &p,int k,int v){
if(!p)p=++T;
if(k>19){c[p]^=1;return;}
ins(son[p][(v>>k)&1ll],k+1,v);
pushup(p);
}
void addall(int p){
if(!p)return;
swap(son[p][0],son[p][1]);
addall(son[p][0]);
pushup(p);
}
int Fa[500005],a[500005],tag[500005];
void dfs(int u,int fa){
Fa[u]=fa,tag[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
ins(root[u],0,a[v]);dfs(v,u);
}
}
int ask(int p){
return w[p];
}
void upd(int u,int k){
if(Fa[u])ins(root[Fa[u]],0,a[u]+tag[Fa[u]]);
a[u]-=k;
if(Fa[u])ins(root[Fa[u]],0,a[u]+tag[Fa[u]]);
}
void add(int u){
if(Fa[u])upd(Fa[u],-1);
tag[u]++;addall(root[u]);
}
int qry(int u){
return (a[Fa[u]]+tag[Fa[Fa[u]]])^ask(root[u]);
}
signed main(){
int n=read(),m=read();
for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
for(int i=1;i<=n;i++)a[i]=read();
dfs(1,0);
while(m--){
int op=read();
if(op==1){
int x=read();
add(x);
}
else if(op==2){
int x=read(),v=read();
upd(x,v);
}
else {
int x=read();
printf("%lld\n",qry(x));
}
}
return 0;
}