颓废

· · 个人记录

记录碰到的题。(是颓废记录所以你不能期望它们有多难)。

常回来看看,填坑练码力。

大家看到我一直不补这些题的代码可以直接在犇犇 diss 我。

P4062

P7882 的全局版本,P4062 存在 polylog 做法。

两个做法我都写!

polylog

考虑分治,常规 trick 之:

  1. 将一个存在严格众数的序列任意分成两半,肯定存在一个序列其拥有严格众数且恰好为原序列的严格众数。
  2. 一个序列的本质不同前缀众数只有 O(\log n) 种。

易证,且一个序列的绝对众数不超过一个。

直接分治统计即可,O(n\log^2 n) 是容易做到的。

懒得写了,搞个 O(n\log^3 n),常数很小。

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N=5e5+10;
int n,a[N],typ,b[N],cnt,sum[N],tot,ct[N];ll ans;

inline void Solve(int l,int r){
    if(l==r){++ans;return ;}
    int mid=(l+r)>>1,now=0;cnt=tot=sum[mid+1]=0;
    for(int i=mid+1;i<=r;++i){++ct[a[i]];b[++cnt]=now=(ct[a[i]]>ct[now]?a[i]:now);}
    for(int i=mid+1;i<=r;++i) ct[a[i]]=0;
    for(int i=mid;i>=l;--i){++ct[a[i]];b[++cnt]=now=(ct[a[i]]>ct[now]?a[i]:now);}
    for(int i=l;i<=mid;++i) ct[a[i]]=0;
    sort(b+1,b+1+cnt);cnt=unique(b+1,b+1+cnt)-b-1;
    for(int d=1;d<=cnt;++d){
        int x=b[d];
        for(int i=mid;i>=l;--i) sum[i]=sum[i+1]+(a[i]==x?1:-1);
        for(int i=mid;i>=l;--i) sum[i]=-sum[i];
        sort(sum+l,sum+mid+1);now=-1;
        for(int i=mid+1;i<=r;++i){
            now+=(a[i]==x?1:-1);
            ans+=upper_bound(sum+l,sum+mid+1,now)-sum-l;
//          cout<<x<<' '<<i<<' '<<ans<<' '<<now<<'\n';
        }
    }
//  cout<<l<<' '<<mid<<' '<<mid+1<<' '<<r<<' '<<ans<<'\n';
    Solve(l,mid);Solve(mid+1,r);
}

int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n>>typ;for(int i=1;i<=n;++i) cin>>a[i];
    Solve(1,n);cout<<ans<<'\n';
    return 0;
}

sqrt

不要忘了做这个题的初衷!

考虑阈值分治,设阈值为 B

对于长度小于 2B 的子区间直接暴力求出来,是 O(nB) 的。

对于长度大于等于 2B 的子区间,其众数至少出现了 B 次,所以这种数不会超过 \frac{n}{B} 暴力枚举统计即可。

考虑怎么统计,考虑怎么判定一个区间的绝对众数是否是 x,将 a_i=x 的位置视作 1,否则视作 -1。那么一个区间的绝对众数是 x 当且仅当区间和大于零。

也就是:sum_r-sum_{l-1}>0,其中 r-l+1\ge 2B

看上去这是一个二维数点问题,但是可以发现 |sum_{r+1}-sum_r|=1,维护一个桶 t_i 表示 sum_{l-1}=t_i 的数量。每次 sum_r 变化的时候可以 O(1) 维护出取值。

块长开到 27 之后把 O(n\log^2 n) 的代码按在地上打,出题人有好好造数据吗?

#include<bits/stdc++.h>

using namespace std;

const int B=27;
const int N=5e5+5;
const int Max=1e6+10;
typedef long long ll;
int n,a[N],cnt[N],C,b[N],typ,sum[N];ll ans;

struct Bucket{
    int t[Max];
    inline void push(int x){++t[x+N];}
    inline int get(int x){return t[x+N];}
    inline void clear(){for(int i=N-n;i<=N+n;++i) t[i]=0;}
}t;

int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n>>typ;for(int i=1;i<=n;++i) cin>>a[i];
    for(int i=1;i<=n;++i) if((++cnt[a[i]])==B) b[++C]=a[i];
    for(int i=1;i<=n;++i) cnt[a[i]]=0;
    for(int i=1;i<=n;++i){
        int ed=min(i+2*B-2,n),c=0;
        for(int j=i;j<=ed;++j){
            c=max(c,++cnt[a[j]]);
            ans+=(2*c>(j-i+1));
        }
        for(int j=i;j<=ed;++j) cnt[a[j]]=0;
    }
    for(int i=1;i<=C;++i){
        int x=b[i],res=0;
        for(int j=1;j<=n;++j){
            sum[j]=sum[j-1]+(a[j]==x?1:-1);
            if(a[j]==x) res+=t.get(sum[j]-1);
            else res-=t.get(sum[j]);
            if(j>=2*B){int k=j-2*B;t.push(sum[k]);res+=(sum[j]>sum[k]);}
            ans+=res;
        }
        t.clear();
    }
    cout<<ans<<'\n';
    return 0;
}

超级钢琴

很厉害的 trick,现在才写是不是没救了(哭)。

考虑区间和可以表示为 sum_{r}-sum_{l-1},发现 k 只有 5\times 10^5,做法肯定是直接找出前 k 大的区间。

感觉思路非常高妙完全不知道怎么想出来的,用 $(x,l,r)$ 三元组表示 $\max_{i\in[l,r]}(sum_i)-sum_{x-1}$,权值可以 $O(1)$ 用 ST 表求出来。 考虑我们选了 $(x,l,r)$ 这个三元组会发生什么,设 $p=\operatorname{argmax}(sum_{l\dots r})$,则原来的三元组可以被分成两部分:$(x,l,p-1)$ 和 $(x,p+1,r)$,然后令答案加上 $sum_{p}-sum_{x-1}$ 即可。 用 `priority_queue` 做到 $O(k\log n)$。 ```cpp #include<bits/stdc++.h> #define int long long using namespace std; const int N=5e5+10,B=20; int a[N],n,k,sum[N],l,r,ans; struct node{ int x,l,r,p; node(int x=0,int l=0,int r=0,int p=0):x(x),l(l),r(r),p(p){;} bool operator <(const node t)const{return (sum[p]-sum[x-1])<(sum[t.p]-sum[t.x-1]);} }; priority_queue < node > Q; struct ST{ int f[B+1][N]; inline int argmax(int x,int y){return sum[x]>sum[y]?(x):(y);} inline void init(){ for(int i=1;i<=n;++i) f[0][i]=i; for(int i=1;i<=__lg(n);++i){ for(int j=1;j<=n-(1<<i)+1;++j) f[i][j]=argmax(f[i-1][j],f[i-1][j+(1<<(i-1))]); } } inline int ask(int l,int r){ int x=__lg(r-l+1); return argmax(f[x][l],f[x][r-(1<<x)+1]); } }s; inline void Push(int x,int l,int r){ if(l>r) return ; Q.push(node(x,l,r,s.ask(l,r))); } signed main(){ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); cin>>n>>k>>l>>r;for(int i=1;i<=n;++i) cin>>a[i],sum[i]=sum[i-1]+a[i]; s.init();for(int i=1;i<=n-l+1;++i){int ed=min(i+r-1,n);Push(i,i+l-1,ed);} while(k--){ node tmp=Q.top();Q.pop(); ans+=sum[tmp.p]-sum[tmp.x-1]; Push(tmp.x,tmp.l,tmp.p-1); Push(tmp.x,tmp.p+1,tmp.r); } cout<<ans<<'\n'; return 0; } ``` ## [等这场战争结束之后](https://www.luogu.com.cn/problem/P5064) 非常 naive 的做法。 先离散化,假装没有重复元素,有的话离散化的时候分开就行了,不影响答案。 考虑经典 trick,建出操作树在之上 dfs,则只需要修改和撤销。 考虑求 $k$ 小值的 trick 有哪些,整体二分和线段树都不是很好做,考虑值域分块。 提前存好 $rev_i$ 表示值为 $i$ 的节点是哪个,对值分块,用 $f_{i,x}$ 表示以 $i$ 为根的并查集中,在第 $x$ 个块的数有多少个,这是容易支持撤销的。 定位到块之后直接暴力遍历时空都是 $O(n\sqrt n)$ 的,空间上过不去。考虑常规 trick 之逐块处理即可做到 $O(n)$ 空间,但是此时时间复杂度的瓶颈变成了并查集,是 $O(n\sqrt n\log n)$ 的,需要调调块长。 注意你需要把 $O(n\sqrt n)$ 次递归给办了。 ```cpp #include<bits/stdc++.h> #define mk make_pair #define sec second #define fi first using namespace std; typedef pair<int,int> pii; const int N=1e5+10; int siz[N],n,m,op[N],X[N],Y[N],ys[N],tot,C,top,fa[N],rev[N]; int val[N],ans[N],lenth,lpos[1010],rpos[1010],now,a[N]; vector < int > v[N],p;pii stk[N]; inline void init(int id){for(int i=1;i<=n;++i) fa[i]=i,siz[i]=1,val[i]=(lpos[id]<=a[i]&&a[i]<=rpos[id]);} int root(int x){while(x!=fa[x]) x=fa[x];return x;} inline bool merge(int x,int y){ x=root(x);y=root(y);if(x==y) return false; if(siz[x]>siz[y]) swap(x,y); fa[x]=y;siz[y]+=siz[x];val[y]+=val[x]; stk[++top]=mk(x,y);return true; } inline void back(pii z){fa[z.fi]=z.fi;siz[z.sec]-=siz[z.fi];val[z.sec]-=val[z.fi];} inline void ask(int x){ if(Y[x]==-1) return ; int bel=root(X[x]); if(Y[x]>siz[bel]){Y[x]=-1;ans[x]=-1;return ;} if(val[bel]<Y[x]) Y[x]-=val[bel]; else { for(int i=lpos[now];i<=rpos[now];++i){ if(root(rev[i])==bel){ if(!(--Y[x])){Y[x]=-1;ans[x]=ys[i];return ;} } } } } void dfs(int x){ int flag=0; if(x){if(op[x]==1) flag=merge(X[x],Y[x]);} if(flag||op[x]!=1) p.emplace_back(x); for(auto i:v[x]) dfs(i); if(op[x]==1&&flag) p.emplace_back(-1),back(stk[top--]); } inline void Oper(){ for(int x:p){ if(~x){ if(op[x]==1) merge(X[x],Y[x]); else if(op[x]==3) ask(x); }else back(stk[top--]); } } int main(){ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); cin>>n>>m;lenth=sqrt(n)*1.5;C=(n-1)/lenth+1; for(int i=1;i<=C;++i) lpos[i]=(i-1)*lenth+1,rpos[i]=i*lenth; for(int i=1;i<=n;++i){cin>>a[i];ys[++tot]=a[i];val[i]=i;} sort(ys+1,ys+1+tot);rpos[C]=n; for(int i=1;i<=n;++i) rev[(a[i]=(val[lower_bound(ys+1,ys+1+tot,a[i])-ys]++))]=i; for(int i=1;i<=m;++i){cin>>op[i]>>X[i];if(op[i]!=2) cin>>Y[i];} for(int i=1;i<=m;++i){//离线出操作树 if(op[i]!=2) v[i-1].emplace_back(i); else v[X[i]].emplace_back(i); } init(0);dfs(0); for(int i=1;i<=C;++i){now=i;init(i);Oper();} for(int i=1;i<=m;++i) if(op[i]==3) cout<<ans[i]<<'\n'; return 0; } ``` ## [TEST_107](https://www.luogu.com.cn/problem/P9991) 考虑从区间中删除颜色,考察一个颜色相邻两次出现的位置,称作 $x,y$,有以下三种情况可能产生贡献: 1. $x< l \le y\le r$,产生 $y-l$ 的贡献。 2. $l\le x \le y\le r$,产生 $y-x-1$ 的贡献。 3. $l\le x \le r< y$,产生 $r-x$ 的贡献。 这三种贡献都可以扫描线,时间复杂度 $O(n\log n)$。 记得处理边界情况哈。 懒得卡常就又敲了一个 TreeArray。 省略了快读。 ```cpp #include<bits/stdc++.h> using namespace std; #define fi first #define endl '\n' #define sec second typedef pair<int,int> pii; const int N=2e6+10; int n,m,a[N],pre[N],ans[N],L[N],R[N]; vector < pii > v[N]; struct Segment{ #define ls now<<1 #define rs now<<1|1 #define mid ((l+r)>>1) int tag[N<<2]; inline void chkmax(int &x,int y){(x<y)&&(x=y);} void build(int now,int l,int r){ tag[now]=-1e9;if(l==r) return ; build(ls,l,mid);build(rs,mid+1,r); } void upd(int now,int l,int r,int x,int y,int v){ if(x>y||x>r||y<l||v<=tag[now]) return ; if(x<=l&&r<=y) return chkmax(tag[now],v); upd(ls,l,mid,x,y,v);upd(rs,mid+1,r,x,y,v); } int ask(int now,int l,int r,int to){ if(l==r) return tag[now]; return max((mid>=to?ask(ls,l,mid,to):ask(rs,mid+1,r,to)),tag[now]); } }t; struct TreeArray{ int tr[N];inline int lowbit(const int &x){return x&(-x);} inline void add(int i,int x){for(;i;i-=lowbit(i)) tr[i]=max(tr[i],x);} inline int ask(int i){int res=0;for(;i<=n;i+=lowbit(i)) res=max(res,tr[i]);return res;} }s; int main(){ cin>>n>>m;for(int i=1;i<=n;++i) cin>>a[i]; for(int i=1;i<=m;++i){ cin>>L[i]>>R[i]; v[R[i]].emplace_back(L[i],i); } for(int i=1;i<=n;++i){ t.upd(1,1,n,pre[a[i]]+1,i,i);pre[a[i]]=i; for(auto x:v[i]) ans[x.sec]=max(ans[x.sec],t.ask(1,1,n,x.fi)-x.fi); } t.build(1,1,n);for(int i=1;i<=n;++i) pre[a[i]]=0; for(int i=1;i<=n;++i){ if(pre[a[i]]) s.add(pre[a[i]],i-pre[a[i]]-1); pre[a[i]]=i;for(auto x:v[i]) ans[x.sec]=max(ans[x.sec],s.ask(x.fi)); } t.build(1,1,n); for(int i=1;i<=n;++i){pre[i]=n+1;v[i].clear();} for(int i=1;i<=m;++i) v[L[i]].emplace_back(R[i],i); for(int i=n;i;--i){ t.upd(1,1,n,i,pre[a[i]]-1,-i);pre[a[i]]=i; for(auto x:v[i]) ans[x.sec]=max(ans[x.sec],x.fi+t.ask(1,1,n,x.fi)); } for(int i=1;i<=m;++i) cout<<ans[i]<<'\n'; return 0; } ``` ## [TEST_90](https://www.luogu.com.cn/problem/P9990) 考虑常规扫描线,求一个区间所有子区间的贡献不难想到维护线段树历史和。 类似区间数颜色地维护区间颜色数。然后我们只用保留区间中为奇数的位置,同时要支持区间加,所以再存 $f_i,g_i$ 表示为奇数和偶数的位置的个数。 矩阵可以做到交换两个数,就做完了。 被卡空间了,以后再修: ```cpp #include<bits/stdc++.h> #define rep(k) for(int k=0;k<3;++k) #define mid ((l+r)>>1) #define rs now<<1|1 #define sec second #define ls now<<1 #define fi first using namespace std; typedef long long ll; typedef pair<int,int> pii; namespace Fread { const int SIZE=1<<21;char buf[SIZE],*S,*T; inline char getchar() {if(S==T){T=(S=buf)+fread(buf,1,SIZE,stdin);if(S==T)return '\n';}return *S++;} } namespace Fwrite { const int SIZE=1<<21; char buf[SIZE],*S=buf,*T=buf+SIZE; inline void flush(){fwrite(buf,1,S-buf,stdout);S=buf;} inline void putchar(char c){*S++=c;if(S==T)flush();} struct POPOSSIBLE{~POPOSSIBLE(){flush();}}ztr; } #define getchar Fread :: getchar #define putchar Fwrite :: putchar namespace Fastio{ struct Reader{ template<typename T> Reader& operator >> (T& x) { char c=getchar();T f=1; while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}x=0; while(c>='0'&&c<='9'){x=x*10+(c-'0');c=getchar();}x*=f; return *this; } Reader(){} }cin; struct Writer{ template<typename T> Writer& operator << (T x) { if(x==0){putchar('0');return *this;} if(x<0){putchar('-');x=-x;} static int sta[45];int top=0; while(x){sta[++top]=x%10;x/=10;} while(top){putchar(sta[top]+'0');--top;} return *this; } Writer& operator << (char c) {putchar(c);return *this;} Writer(){} }cout; } #define endl '\n' #define cin Fastio :: cin #define cout Fastio :: cout const int Max=3,N=5e5+10; struct Matrix{ ll a[Max][Max]; ll* operator [](const int x){return a[x];} Matrix(){rep(i) rep(j) a[i][j]=0;} inline void init(){rep(i) rep(j) a[i][j]=(i==j);} inline bool chk(){rep(i) rep(j) if(a[i][j]!=(i==j)) return true;return false;} Matrix operator +(Matrix &t){Matrix z;rep(i) rep(j) z[i][j]=a[i][j]+t[i][j];return z;} Matrix operator *(Matrix &t){Matrix z=Matrix();rep(i) rep(k) rep(j) z[i][j]+=a[i][k]*t[k][j];return z;} }s,h,tr[N<<2],tag[N<<2]; int n,a[N],m,pre[N];ll ans[N]; vector < pii > v[N]; inline void init(){s[0][0]=s[1][2]=s[2][1]=1;h[0][0]=h[1][0]=h[1][1]=h[2][2]=1;} inline void pushup(int now){tr[now]=tr[ls]+tr[rs];} inline void mul(int now,Matrix &v){tag[now]=tag[now]*v;tr[now]=tr[now]*v;} inline void pushdown(int now){if(tag[now].chk()){mul(ls,tag[now]);mul(rs,tag[now]);tag[now].init();}} void build(int now,int l,int r){ tag[now].init();tr[now][0][2]=r-l+1;if(l==r) return ; build(ls,l,mid);build(rs,mid+1,r); } void upd(int now,int l,int r,int x,int y,Matrix &v){ if(x>y||x>r||y<l) return ; if(x<=l&&r<=y) return mul(now,v); pushdown(now);upd(ls,l,mid,x,y,v);upd(rs,mid+1,r,x,y,v);pushup(now); } int ask(int now,int l,int r,int x,int y){ if(x>y||x>r||y<l) return 0; if(x<=l&&r<=y) return tr[now][0][0]; pushdown(now);return ask(ls,l,mid,x,y)+ask(rs,mid+1,r,x,y); } int main(){ init();cin>>n;build(1,1,n);for(int i=1;i<=n;++i) cin>>a[i]; cin>>m;for(int i=1,l,r;i<=m;++i){cin>>l>>r;v[r].emplace_back(l,i);} for(int i=1;i<=n;++i){ upd(1,1,n,pre[a[i]]+1,i,s);pre[a[i]]=i;mul(1,h); for(auto x:v[i]) ans[x.sec]=ask(1,1,n,x.fi,i); } for(int i=1;i<=m;++i) cout<<ans[i]<<'\n'; return 0; } ``` ## [TEST_176](https://www.luogu.com.cn/problem/P11622) 平衡树有交并板子,降智了才调这么久。 需要注意的是 `x>>1` 是下取整,`x/2` 是向零取整。 ```cpp #include<bits/stdc++.h> using namespace std; namespace Fread { const int SIZE=1<<21;char buf[SIZE],*S,*T; inline char getchar() {if(S==T){T=(S=buf)+fread(buf,1,SIZE,stdin);if(S==T)return '\n';}return *S++;} } namespace Fwrite { const int SIZE=1<<21; char buf[SIZE],*S=buf,*T=buf+SIZE; inline void flush(){fwrite(buf,1,S-buf,stdout);S=buf;} inline void putchar(char c){*S++=c;if(S==T)flush();} struct POPOSSIBLE{~POPOSSIBLE(){flush();}}ztr; } #define getchar Fread :: getchar #define putchar Fwrite :: putchar namespace Fastio{ struct Reader{ template<typename T> Reader& operator >> (T& x) { char c=getchar();T f=1; while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}x=0; while(c>='0'&&c<='9'){x=x*10+(c-'0');c=getchar();}x*=f; return *this; } Reader(){} }cin; struct Writer{ template<typename T> Writer& operator << (T x) { if(x==0){putchar('0');return *this;} if(x<0){putchar('-');x=-x;} static int sta[45];int top=0; while(x){sta[++top]=x%10;x/=10;} while(top){putchar(sta[top]+'0');--top;} return *this; } Writer& operator << (char c) {putchar(c);return *this;} Writer(){} }cout; } #define fi first #define endl '\n' #define sec second #define int long long #define cin Fastio :: cin #define cout Fastio :: cout typedef pair<int,int> pii; const int N=2e5+10; int n,m,ans[N],id[N],a[N],val[N]; vector < pii > v[N]; mt19937 rnd(time(NULL)); struct Key{ int val,id; Key(int val=0,int id=0):val(val),id(id){;} inline void revs(){val=-val;id=-id;} inline void adds(int v){val+=v;id+=v;} bool operator <(const Key &t)const{return val==t.val?id<t.id:val<t.val;} }; struct node{ int ch[2],key,tag,add,fa;Key val; node(int c0=0,int c1=0,int _key=0,int _tag=0,int _add=0,Key _val=Key(),int _fa=0){ ch[0]=c0,ch[1]=c1,key=_key,tag=_tag,add=_add,val=_val,fa=_fa; } #define ch(k,i) tr[k].ch[i] #define val(k) tr[k].val #define key(k) tr[k].key #define tag(k) tr[k].tag #define add(k) tr[k].add #define fa(k) tr[k].fa }tr[N]; int rev[N],rt,tot; inline void pushup(int now){fa(ch(now,0))=fa(ch(now,1))=now;fa(0)=0;} inline void rever(int now){swap(ch(now,0),ch(now,1));tag(now)^=1;add(now)=-add(now);val(now).revs();} inline void Add(int now,int v){add(now)+=v;val(now).adds(v);} inline void pushdown(int now){ if(tag(now)){rever(ch(now,0));rever(ch(now,1));tag(now)=0;} if(add(now)){Add(ch(now,0),add(now));Add(ch(now,1),add(now));add(now)=0;} } void split(int k,Key v,int &x,int &y){ if(!k){x=y=0;return ;} pushdown(k); if(val(k)<v){x=k;split(ch(x,1),v,ch(x,1),y);} else {y=k;split(ch(y,0),v,x,ch(y,0));} pushup(k); } int merge(int x,int y){ if(!x||!y) return x|y; if(key(x)<key(y)){pushdown(x);ch(x,1)=merge(ch(x,1),y);pushup(x);return x;} else{pushdown(y);ch(y,0)=merge(x,ch(y,0));pushup(y);return y;} } int Merge(int x,int y){ if(!x||!y) return x|y; if(key(x)>key(y)) swap(x,y); int tx,ty;pushdown(x);pushdown(y); split(y,val(x),tx,ty); ch(x,0)=Merge(ch(x,0),tx); ch(x,1)=Merge(ch(x,1),ty); pushup(x);return x; } inline void Alldown(int now){ if(now!=rt) Alldown(fa(now)); pushdown(now); } inline void Operate(int v){ Key w=Key(v>>1,LLONG_MAX); int x,y;split(rt,w,x,y); rever(x);Add(x,v);rt=Merge(x,y); } inline int Newnode(int v){key(++tot)=rnd();val(tot)=Key(v,rnd());return tot;} inline int ins(int v){ int d=Newnode(v),x,y; split(rt,val(d),x,y); rt=merge(merge(x,d),y); return d; } inline int ask(int d){ Alldown(d);return val(d).val; } void Push(int now){ pushdown(now);pushup(now); if(ch(now,0)) Push(ch(now,0)); if(ch(now,1)) Push(ch(now,1)); } signed main(){ cin>>n>>m;for(int i=1;i<=n;++i) cin>>a[i]; for(int i=1,l,r;i<=m;++i){ cin>>val[i]>>l>>r; v[r+1].emplace_back(i,-1); v[l].emplace_back(i,1); } for(int i=1;i<=n+1;++i){ for(auto x:v[i]){ if(x.sec==1) id[x.fi]=ins(val[x.fi]); else ans[x.fi]=ask(id[x.fi]); } Operate(a[i]); } for(int i=1;i<=m;++i) cout<<ans[i]<<'\n'; return 0; } ``` ## [Yuno loves sqrt technology II](https://www.luogu.com.cn/problem/P5047) 之前懒得写了,现在来补一下。 二次离线莫队板子,需要用到 $O(\sqrt n)$ 修改,$O(1)$ 区间查询的复杂度平衡分块。 错的唯一一个地方是离散化。 ```cpp #include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e5+10; int pos[N],n,m,a[N],lenth,ys[N],tot,C;ll ans[N],F[N],G[N],res[N],lpos[N],rpos[N]; struct Query{ int id,l,r; Query(int id=0,int l=0,int r=0):id(id),l(l),r(r){;} bool operator <(const Query t)const{return (pos[l]==pos[t.l])?((pos[l]&1)?r<t.r:r>t.r):l<t.l;} }q[N]; struct Block{//O(\sqrt) 前缀加+x,O(1) 单点求值 int tag[N],val[N]; inline void add(int p,int x){ int ed=pos[p];for(int i=1;i<ed;++i) tag[i]+=x; for(int i=lpos[ed];i<=p;++i) val[i]+=x; } inline int ask(int p){return tag[pos[p]]+val[p];} inline int ask(int l,int r){return l<=r?ask(l)-ask(r+1):0;} }b; struct Oper{ int op,typ,id,l,r; Oper(int op=0,int typ=0,int id=0,int l=0,int r=0): op(op),typ(typ),id(id),l(l),r(r){;} }; vector < Oper > v[N]; inline void Deal(){//处理二次离线 for(int i=1;i<=n;++i){ b.add(a[i],1); F[i]=F[i-1]+b.ask(a[i]+1,n); G[i]=G[i-1]+b.ask(1,a[i]-1); for(auto x:v[i]){ ll val=0;int op=x.typ; for(int j=x.l;j<=x.r;++j){ if(op==0) val+=b.ask(a[j]+1,n); else val+=b.ask(1,a[j]-1); } ans[x.id]+=val*x.op; } } } int main(){ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); cin>>n>>m;for(int i=1;i<=n;++i) cin>>a[i],ys[++C]=a[i]; lenth=sqrt(n);for(int i=1;i<=n;++i) pos[i]=(i-1)/lenth+1; tot=pos[n];for(int i=1;i<=tot;++i) lpos[i]=(i-1)*lenth+1,rpos[i]=i*lenth; sort(ys+1,ys+1+C);C=unique(ys+1,ys+1+C)-ys-1;rpos[tot]=n; for(int i=1;i<=n;++i) a[i]=lower_bound(ys+1,ys+1+C,a[i])-ys; for(int i=1;i<=m;++i){cin>>q[i].l>>q[i].r;q[i].id=i;}//一次离线 sort(q+1,q+1+m);int L=1,R=0; for(int i=1;i<=m;++i){//二次离线 if(R<q[i].r) v[L-1].emplace_back(-1,0,i,R+1,q[i].r),R=q[i].r; if(L>q[i].l) v[R].emplace_back(1,1,i,q[i].l,L-1),L=q[i].l; if(R>q[i].r) v[L-1].emplace_back(1,0,i,q[i].r+1,R),R=q[i].r; if(L<q[i].l) v[R].emplace_back(-1,1,i,L,q[i].l-1),L=q[i].l; } Deal();L=1,R=0; for(int i=1;i<=m;++i){ if(R<q[i].r) ans[i]+=F[q[i].r]-F[R],R=q[i].r; if(L>q[i].l) ans[i]-=G[L-1]-G[q[i].l-1],L=q[i].l; if(R>q[i].r) ans[i]-=F[R]-F[q[i].r],R=q[i].r; if(L<q[i].l) ans[i]+=G[q[i].l-1]-G[L-1],L=q[i].l; res[q[i].id]=(ans[i]+=ans[i-1]); } for(int i=1;i<=m;++i) cout<<res[i]<<'\n'; return 0; } ``` ## [来者不拒,去者不追](https://www.luogu.com.cn/problem/P5501) 莫队二次离线,考虑这样刻画问题:如果 $a_l<a_r$ 则会造成 $a_r$ 的贡献,最后再加上区间和就行了。 移动端点时造成的贡献是 $x\sum\limits_{i=l}^r[a_i<x]+\sum\limits_{i=l}^r[a_i>x]a_i$。 跟上面的题本质相同,注意函数返回值要开 `long long`。 ```cpp #include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=5e5+10; int pos[N],n,m,a[N],lenth,ys[N],tot,C;ll ans[N],F[N],res[N],lpos[N],rpos[N],sum[N]; struct Query{ int id,l,r; Query(int id=0,int l=0,int r=0):id(id),l(l),r(r){;} bool operator <(const Query t)const{return (pos[l]==pos[t.l])?((pos[l]&1)?r<t.r:r>t.r):l<t.l;} }q[N]; struct Block{//O(\sqrt) 前缀加+x,O(1) 单点求值 ll tag[N],val[N]; inline void add(int p,int x){ int ed=pos[p];for(int i=1;i<ed;++i) tag[i]+=x; for(int i=lpos[ed];i<=p;++i) val[i]+=x; } inline ll ask(int p){return tag[pos[p]]+val[p];} inline ll ask(int l,int r){return l<=r?ask(l)-ask(r+1):0;} }b,c; struct Oper{ int op,id,l,r; Oper(int op=0,int id=0,int l=0,int r=0): op(op),id(id),l(l),r(r){;} }; vector < Oper > v[N]; inline void Deal(){//处理二次离线 for(int i=1;i<=n;++i){ b.add(a[i],1);c.add(a[i],ys[a[i]]); F[i]=F[i-1]+b.ask(1,a[i]-1)*ys[a[i]]+c.ask(a[i]+1,n); for(auto x:v[i]){ ll val=0; for(int j=x.l;j<=x.r;++j){ val+=b.ask(1,a[j]-1)*ys[a[j]]+c.ask(a[j]+1,n); } ans[x.id]+=val*x.op; } } } int main(){ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); cin>>n>>m;for(int i=1;i<=n;++i) cin>>a[i],ys[++C]=a[i],sum[i]=sum[i-1]+a[i]; lenth=sqrt(n);for(int i=1;i<=n;++i) pos[i]=(i-1)/lenth+1; tot=pos[n];for(int i=1;i<=tot;++i) lpos[i]=(i-1)*lenth+1,rpos[i]=i*lenth; sort(ys+1,ys+1+C);C=unique(ys+1,ys+1+C)-ys-1;rpos[tot]=n; for(int i=1;i<=n;++i) a[i]=lower_bound(ys+1,ys+1+C,a[i])-ys; for(int i=1;i<=m;++i){cin>>q[i].l>>q[i].r;q[i].id=i;}//一次离线 sort(q+1,q+1+m);int L=1,R=0; for(int i=1;i<=m;++i){//二次离线 if(R<q[i].r) v[L-1].emplace_back(-1,i,R+1,q[i].r),R=q[i].r; if(L>q[i].l) v[R].emplace_back(1,i,q[i].l,L-1),L=q[i].l; if(R>q[i].r) v[L-1].emplace_back(1,i,q[i].r+1,R),R=q[i].r; if(L<q[i].l) v[R].emplace_back(-1,i,L,q[i].l-1),L=q[i].l; } Deal();L=1,R=0; for(int i=1;i<=m;++i){ if(R<q[i].r) ans[i]+=F[q[i].r]-F[R],R=q[i].r; if(L>q[i].l) ans[i]-=F[L-1]-F[q[i].l-1],L=q[i].l; if(R>q[i].r) ans[i]-=F[R]-F[q[i].r],R=q[i].r; if(L<q[i].l) ans[i]+=F[q[i].l-1]-F[L-1],L=q[i].l; res[q[i].id]=(ans[i]+=ans[i-1])+sum[q[i].r]-sum[q[i].l-1]; } for(int i=1;i<=m;++i) cout<<res[i]<<'\n'; return 0; } ``` ## [GOSICK](https://www.luogu.com.cn/problem/P5398) 这哪有黑?考虑莫队二次离线。 加入一个数 $x$ 所产生的贡献是:$\sum\limits_{i=l}^r[a_i|x]+\sum\limits_{i=l}^r[x|a_i]-\sum\limits_{i=l}^r[a_i=x]+1$。 思考怎么快速维护这些东西,$\sum\limits_{i=l}^r[a_i=x]$ 是容易的,直接开个桶,其他两项似乎不好处理。 考虑对加入的 $x$ 阈值分治: + 对于 $x\le B$,每次加入数的时候暴力处理其倍数的个数,其因数的个数直接枚举就行了。 + 对于 $x>B$,其倍数个数直接暴力,其因数中小于等于 $B$ 的部分暴力枚举,大于 $B$ 的部分暴力预处理。 单次时间是 $O(B+\frac nB)$ 的,$B$ 取 $\sqrt n$。 注意跟二离模板一样的细节即可,貌似不是很卡常? ## [地牢 3](https://www.luogu.com.cn/problem/P7408) 哦哦哦模拟赛题启动。 手玩一下容易发现单次 $O(n)$ 的贪心,从前往后贪心,假如当前到了 $i$,其后面第一个开销小于等于 $B_i$ 的位置为 $j$。那么在 $i$ 处的加油策略是:如果能加油加到能行驶到 $j$,加到这么多后直接走到 $j$,否则加满,然后走到 $i+1$。无解是好判的。 考虑维护这个过程,发现难点在于:满油时的油量并不固定导致点 $i$ 走到的点可能会一直在变。 我们思考 $i$ 能走到的有效点究竟有多少种? 如果我们是被迫加到了满油,设 $p$ 是我们能走到的最远的点,那么我们肯定是去 $\operatorname{argmax}\limits_{j\in(i,p]}(a_j)$ 继续加油,原因显然。 下面是赛时没有想到的: 我们换一种方式刻画,翻过来单调栈。从底至顶严格单减的单调栈,那么 $i$ 有可能走到 $j$ 去加油当且仅当 $i$ 把 $j$ 弹出去了。 从这个角度出发发现有效点之和最多只有 $O(n)$ 个,将 $x$ 排序之后用 LCT 暴力维护内向树即可。 貌似稍微有点细节? 其实还有更不吃操作的做法好像。 先考虑 Subtask 3 的部分分,这是在提醒我们从后往前扫描线计算贡献,因为如果我们使用最优策略,那么我们走到终点时肯定没有剩油。 还是考虑刻画单调栈的过程,思考在 $U$ 一定时的策略,从反悔贪心入手进行计算。 思考:如果我们导致了退栈,会发生什么。用 $x$ 表示到栈顶的距离,用 $y$ 表示到栈顶下面一个数的距离。那么当 $U\ge y$ 时,由栈顶到栈顶下面一个数的油可以直接由 $i$ 出,所以可以回退掉 $y-x$ 的油量,否则只能回退掉 $y-x-(y-U)=U-x$ 的油量。 合并一下,就相当于回退掉 $\max(\min(U,y)-x,0)$ 的油。 使用树状数组维护区间加一次函数单点求值可以获得 31 分好成绩,缝上暴力可以获得 42 分。 用 $f(S,T)$ 表示答案,则如果令 $p$ 为和 $T$ 距离在 $U$ 以内的花费最少的点,那么 $f(S,T)=f(S,N+1)-f(m,N+1)+dis(m,T)\times b_m$,转化成部分分。 ```cpp #include<bits/stdc++.h> #define int long long using namespace std; const int N=3e5+10; struct node{int U,id,opt;node(int U=0,int id=0,int opt=0):U(U),id(id),opt(opt){;}}; int n,m,A[N],B[N],stk[N],top,pos[N],ys[N],tot,L[N],R[N],lim[N],res[N]; vector < node > v[N]; struct ST{ int f[20][N]; inline int argmax(int x,int y){return B[x]==B[y]?max(x,y):(B[x]<B[y]?(x):(y));} inline void init(){ for(int i=1;i<=n;++i) f[0][i]=i; for(int i=1;i<=__lg(n);++i){ for(int j=1;j<=n-(1<<i)+1;++j) f[i][j]=argmax(f[i-1][j],f[i-1][j+(1<<(i-1))]); } } inline int ask(int l,int r){ int x=__lg(r-l+1); return argmax(f[x][l],f[x][r-(1<<x)+1]); } }s; struct TreeArray{ int tr[N];inline int lowbit(const int &x){return x&(-x);} inline void add(int i,int x){for(;i<=tot;i+=lowbit(i)) tr[i]+=x;} inline void upd(int l,int r,int v){if(l>r) return ;add(l,v);add(r+1,-v);} inline int ask(int i){int res=0;for(;i;i-=lowbit(i)) res+=tr[i];return res;} }t1,t2; inline int ask(int p){return t1.ask(p)*ys[p]+t2.ask(p);} inline void chg(int x,int y,int v){//+=min(U,y)-x int pos=lower_bound(ys+1,ys+1+tot,y)-ys; int st=lower_bound(ys+1,ys+1+tot,x)-ys; //[x,pos):k+=1,b-=x t1.upd(st,pos-1,v);t2.upd(st,pos-1,-x*v); //[pos,tot]:b+=y-x; t2.upd(pos,tot,(y-x)*v); } inline void chg2(int x,int v){//+=min(x,U) int pos=lower_bound(ys+1,ys+1+tot,x)-ys; t1.upd(1,pos-1,v);t2.upd(pos,tot,x*v); } struct Segment{ #define ls now<<1 #define rs now<<1|1 #define mid ((l+r)>>1) int Max[N<<2];inline void pushup(int now){Max[now]=max(Max[ls],Max[rs]);} void build(int now,int l,int r){if(l==r){Max[now]=A[l];return ;}build(ls,l,mid);build(rs,mid+1,r);pushup(now);} int ask(int now,int l,int r,int x,int y){ if(x>y||x>r||y<l) return 0; if(x<=l&&r<=y) return Max[now]; return max(ask(ls,l,mid,x,y),ask(rs,mid+1,r,x,y)); } }t; signed main(){ // freopen("dungeon.in","r",stdin);freopen("dungeon.out","w",stdout); ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); cin>>n>>m;for(int i=1;i<=n;++i) cin>>A[i],pos[i]=pos[i-1]+A[i-1]; t.build(1,1,n);pos[n+1]=pos[n]+A[n];for(int i=1;i<=n;++i) cin>>B[i]; for(int i=1;i<=m;++i){cin>>L[i]>>R[i]>>lim[i];--R[i];ys[++tot]=lim[i];} sort(ys+1,ys+1+tot);tot=unique(ys+1,ys+1+tot)-ys-1;ys[++tot]=1e18+10;s.init(); for(int i=1;i<=m;++i){ if(t.ask(1,1,n,L[i],R[i])>lim[i]){res[i]=-1;continue;} lim[i]=lower_bound(ys+1,ys+1+tot,lim[i])-ys; v[L[i]].emplace_back(lim[i],i,1); int p=lower_bound(pos+1,pos+1+n,pos[R[i]+1]-ys[lim[i]])-pos; p=max(p,L[i]);p=s.ask(p,R[i]+1); v[p].emplace_back(lim[i],i,-1); res[i]=(pos[R[i]+1]-pos[p])*B[p]; } stk[top=0]=n+1; for(int i=n;i;--i){ while(top&&B[i]<B[stk[top]]){ int x=pos[stk[top]]-pos[i],y=pos[stk[top-1]]-pos[i]; chg(x,y,-B[stk[top]]);--top; } int x=pos[stk[top]]-pos[i];chg2(x,B[i]);stk[++top]=i; for(auto x:v[i]) res[x.id]+=ask(x.U)*x.opt; } for(int i=1;i<=m;++i) cout<<res[i]<<'\n'; return 0; } ``` ## [墓碑密码](https://www.luogu.com.cn/problem/P13497) 发现序列和每个数出现次数双射。 考虑分开计算,用 $f_{j}$ 有 $j$ 个数出现了奇数次的方案,使用多项式计算 $(\sum\limits_{i=0}^{\infty} [i\bmod 2=1] x^i)^{j}(\sum\limits_{i=0}^\infty[i\bmod 2=0]x^i)^{|S|-j}$,这个是不难计算的,后面说。 考虑再预处理一个 $g_k$ 表示从 $S$ 中选 $k$ 个数使得其异或和在 $T$ 中出现过的方案数,到时候直接求 $\sum f_kg_k$。 这个时候如果我们去拼特殊性质能够直接拿到好多分,可喜可贺。 我们期望一种 $O(V)$ 的方式得到?不如先思考一下 $|S|\le 128$ 是为什么。 先思考怎么算 $f_j$: $$ \begin{aligned} f_j&=[x^{n-j}]\left(\frac{1}{1-x^2}\right)^{|S|}\\ &=[n-j\bmod 2=0]{\frac{n-j}2+|S|-1\choose |S|-1}\\ \end{aligned} $$ 把 $f_j$ 翻转再压缩一下: $$ f_j={j+|S|-1\choose |S|-1} $$ 现在它长度只有 $5\times 10^7$ 了。 所以说我们将之转化为了另一个问题:求出 $g_k$ 表示从 $S$ 中选出 $k$ 个数使他们异或和在 $T$ 中出现过。 这是可做题? 以下令 $L=2^{28}$。 考虑使用二元生成函数描述问题,$h_k=[x^k]\prod\limits_{i\in S}(x^iy+1)$。我们要对于所有的 $k\in T$ 把 $h_k$ 加起来就是 $g_k$ 对应的生成函数了。 $x$ 做异或卷积,$y$ 做正常的多项式卷积。 考虑对 $1+x^iy$ 进行 FWT 转点积,$[x^j]FWT(1+x^iy)=\sum\limits_{T}(-1)^{|j\cap T|}[x^T](1+x^iy)$。 所以点值是好求的,$[x^j]FWT(1+x^iy)=(1+(-1)^{|j\cap i|}y)$。 只需要把 $j$ 相同的项乘起来,最后在 IFWT 回去就行了。 $$ \begin{aligned} h_k&=\frac 1L[x^k]FWT\left(\sum_{j=0}^{L-1}\left(\prod_{i\in S} (1+(-1)^{|j\cap i|}y)\right)x^j\right)\\ &=\frac 1L\sum_{j=0}^{L-1}(-1)^{|k\cap j|}\left(\prod_{i\in S} (1+(-1)^{|j\cap i|}y)\right) \end{aligned} $$ 于是我们要求的就是: $$ \begin{aligned} G(y)&= \frac 1L\sum_{k\in T}\sum_{j=0}^{L-1}(-1)^{|j\cap k|}\left(\prod_{i\in S} (1+(-1)^{|j\cap i|}y)\right)\\ &=\frac 1L\sum_{j=0}^{L-1}\sum_{k\in T}(-1)^{|j\cap k|}\left(\prod_{i\in S} (1+(-1)^{|j\cap i|}y)\right) \end{aligned} $$ 敏锐的注意到后面求积号里只有可能有 $(1+y)$ 和 $(1-y)$,从此入手我们希望更进一步地化简形式: $$ \begin{aligned} A_i&=\sum_{x\in S}[|x\cap i|\bmod 2=1]\\ B_i&=\sum_{x\in T}[|x\cap i|\bmod 2=1]\\ G(y)&=\frac 1L\sum_{i=0}^{L-1}(|T|-2B_i)(1-y)^{A_i}(1+y)^{|S|-A_i}\\ \end{aligned} $$ 再把 $A_i$ 相同的地方并到一起: $$ G(y)=\frac 1L\sum_{i=0}^{|S|}C_i(1-y)^i(1+y)^{|S|-A_i} $$ 其中 $C_i$ 是贡献系数之和。 现在只需要求出 $A_i,B_i$ 问题即可解决,考虑每次快速计算 $i\to i+1$ 时的变化,用 bitset 即可快速维护。 问题即可在 $O(L+\frac{(|S|+|T|)L}{w}+n|S|)$ 的时间内完成。 ```cpp #include<bits/stdc++.h> using namespace std; namespace Fread { const int SIZE=1<<21;char buf[SIZE],*S,*T; inline char getchar() {if(S==T){T=(S=buf)+fread(buf,1,SIZE,stdin);if(S==T)return '\n';}return *S++;} } namespace Fwrite { const int SIZE=1<<21; char buf[SIZE],*S=buf,*T=buf+SIZE; inline void flush(){fwrite(buf,1,S-buf,stdout);S=buf;} inline void putchar(char c){*S++=c;if(S==T)flush();} struct POPOSSIBLE{~POPOSSIBLE(){flush();}}ztr; } #define getchar Fread :: getchar #define putchar Fwrite :: putchar namespace Fastio{ struct Reader{ template<typename T> Reader& operator >> (T& x) { char c=getchar();T f=1; while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}x=0; while(c>='0'&&c<='9'){x=x*10+(c-'0');c=getchar();}x*=f; return *this; } Reader(){} }cin; struct Writer{ template<typename T> Writer& operator << (T x) { if(x==0){putchar('0');return *this;} if(x<0){putchar('-');x=-x;} static int sta[45];int top=0; while(x){sta[++top]=x%10;x/=10;} while(top){putchar(sta[top]+'0');--top;} return *this; } Writer& operator << (char c) {putchar(c);return *this;} Writer(){} }cout; } #define endl '\n' #define cin Fastio :: cin #define cout Fastio :: cout const int N=1e5+10; const int mod=1e9+7,K=28; const int Max=5e7+10000; struct modint { int val; static int norm(const int& x) { return x < 0 ? x + mod : x; } inline int Mod(const int x){return x>=mod?x-mod:x;} modint inv() const { int a = val, b = mod, u = 1, v = 0, t; while (b > 0) t = a / b, swap(a -= t * b, b), swap(u -= t * v, v); return modint(u); } modint() : val(0) {} modint(const int& m) : val(norm(m)) {} modint operator-() const { return modint(norm(-val)); } modint& operator+=(const modint& o) { return val = Mod(val + o.val), *this; } modint& operator-=(const modint& o) { return val = norm(val - o.val), *this; } modint& operator*=(const modint& o) { return val = static_cast<int>(1ll * val * o.val % mod), *this; } modint operator-(const modint& o) const { return modint(*this) -= o; } modint operator+(const modint& o) const { return modint(*this) += o; } modint operator*(const modint& o) const { return modint(*this) *= o; } friend std::ostream& operator<<(std::ostream& os, const modint& a) { return os << a.val; } }frac[Max],inv[Max],g[N],val[N],f[N]; int n,m,S[N],T[N],Cas,las; bitset < 128 > a[N],b[N],sa,sb; inline void init(const int n=5e7+200){ inv[0]=frac[0]=1;for(int i=1;i<=n;++i) frac[i]=frac[i-1]*i; inv[n]=frac[n].inv();for(int i=n-1;i;--i) inv[i]=inv[i+1]*(i+1); } inline void predo(const int len=5e7){ modint v=inv[n-1]; for(int i=0;i<=len;++i) frac[i]=frac[i+n-1]*v*inv[i]; for(int i=1;i<=len;++i) frac[i]+=frac[i-1]; } inline modint ask(){ int p;cin>>p;modint res=0; for(int k=0;k<=min(p,n);++k) res+=frac[(p-k)/2]*g[k]; return res; } inline void Plus(){for(int i=n;i;--i) f[i]+=f[i-1];} inline void Minus(){for(int i=n;i;--i) f[i]-=f[i-1];} inline void pre(){for(int i=1;i<=n;++i) f[i]+=f[i-1];} int main(){ // freopen("sleep.in","r",stdin);freopen("sleep.out","w",stdout); init();cin>>n>>m;predo(); for(int i=0;i<n;++i) cin>>S[i]; for(int i=0;i<m;++i) cin>>T[i]; for(int i=0;i<K;++i){ int B=(1<<(i+1))-1; for(int j=0;j<n;++j) a[i][j]=__builtin_popcount(S[j]&B)&1; for(int j=0;j<m;++j) b[i][j]=__builtin_popcount(T[j]&B)&1; } for(int i=0;i<(1<<K);++i){ val[n-sa.count()]+=(int)(m-(2*sb.count())); int tmp=__builtin_ctz(i^((1<<K)-1)); sa^=a[tmp];sb^=b[tmp]; } modint p=1;for(int i=1;i<=K;++i) p*=inv[2]; for(int i=0;i<=n;++i) val[i]*=p; f[0]=1;for(int i=0;i<n;++i) Minus(); for(int i=0;i<=n;++i){ for(int j=0;j<=n;++j) g[j]+=val[i]*f[j]; pre();Plus(); } cin>>Cas;while(Cas--) cout<<(las=ask().val)<<'\n'; return 0; } ``` ## [魔法少女网站](https://www.luogu.com.cn/problem/P6578) 2024 年二月写的题,调了一天最后被卡常,考虑重构。 做法简单来说就是操作分块套序列分块。 思考没有修改操作怎么做,令 $b_i=[x\ge a_i]$,那么答案就是区间内元素全是 $1$ 的区间个数,维护极长 $1$ 连续段,一个极长 $1$ 连续段的贡献是 $\frac{len(len+1)}2$,使用线段树即可快速维护。然后将询问从小到大排序,那么会有若干 $0\to 1$ 的修改,也是容易的。 但是线段树做法大概率对后面的问题没什么启发性,考虑序列分块处理询问,在连续段的端点维护当前连续段的另一个端点是什么,再对于每一个块存一下区间的贡献,暴力查询即可,可能会有一些细节。 考虑存在修改怎么做,不难想到操作分块,对于每一个块内将询问按值排序,再将这个块之前的修改按值排序,不难发现在处理值递增的询问时,这个块之前的修改都是 $0\to 1$ 的,问题在于处理同块的修改,可以选择直接暴力回退。 说起来还是比较简单的,代码实现上可能有一些细节,不精细的话很可能退化到 $O(n\sqrt n\log n)$。 比如将这个块之前的修改排序需要采用归并方法(或者桶排),否则退化。为了好写起见我的意见是把修改和询问分开存。 九十多分,准备找个夜深人静的时候交。 ```cpp // #include <immintrin.h> // #include <emmintrin.h> // #pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native") #include<bits/stdc++.h> using namespace std; typedef long long ll; namespace Fread { const int SIZE=1<<21;char buf[SIZE],*S,*T; inline char getchar() {if(S==T){T=(S=buf)+fread(buf,1,SIZE,stdin);if(S==T)return '\n';}return *S++;} } namespace Fwrite { const int SIZE=1<<21; char buf[SIZE],*S=buf,*T=buf+SIZE; inline void flush(){fwrite(buf,1,S-buf,stdout);S=buf;} inline void putchar(char c){*S++=c;if(S==T)flush();} struct POPOSSIBLE{~POPOSSIBLE(){flush();}}ztr; } #define getchar Fread :: getchar #define putchar Fwrite :: putchar namespace Fastio{ struct Reader{ template<typename T> Reader& operator >> (T& x) { char c=getchar();T f=1; while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}x=0; while(c>='0'&&c<='9'){x=x*10+(c-'0');c=getchar();}x*=f; return *this; } Reader(){} }cin; struct Writer{ template<typename T> Writer& operator << (T x) { if(x==0){putchar('0');return *this;} if(x<0){putchar('-');x=-x;} static int sta[45];int top=0; while(x){sta[++top]=x%10;x/=10;} while(top){putchar(sta[top]+'0');--top;} return *this; } Writer& operator << (char c) {putchar(c);return *this;} Writer(){} }cout; } #define endl '\n' #define cin Fastio :: cin #define cout Fastio :: cout const int N=3e5+9e3; const int B=3e3+10; struct Query{ int opt,x,y,z,id; bool operator <(const Query t)const{return z==t.z?opt>t.opt:z<t.z;} Query(int opt=0,int x=0,int y=0,int z=0,int id=0): opt(opt),x(x),y(y),z(z),id(id){;} }q[N],d[N]; struct Change{int x,y,ti;}c[N]; int n,m,lenth,tot,pos[N],val[N],ne[N],h[N],cnt; int a[N],vis[N],lp[N],rp[N]; ll ans[N]; struct Blocks{ struct Node{ int l,r,cnt;ll ans; #define lpos(k) blk[k].l #define rpos(k) blk[k].r #define Cnt(k) blk[k].cnt #define Ans(k) blk[k].ans }blk[N]; int L[N],R[N],top,v[N],pos[N],lenth,tot;ll res; struct node{int x,flag;node(int x=0,int flag=0):x(x),flag(flag){;}}stk[N]; inline void upd(int x,int typ){ if(v[x]) return ; v[x]=1;++Cnt(pos[x]); int flag=(x!=lpos(pos[x])&&v[x-1])|((x!=rpos(pos[x])&&v[x+1])<<1); //记得写回滚 if(typ) stk[++top]=node(x,flag); if(!flag){L[x]=R[x]=x;++Ans(pos[x]);} else if(flag==1){R[x]=x;R[L[x]=L[x-1]]=x;Ans(pos[x])+=x-L[x]+1;} else if(flag==2){L[x]=x;L[R[x]=R[x+1]]=x;Ans(pos[x])+=R[x]-x+1;} else {int l=L[x-1],r=R[x+1];L[r]=l;R[l]=r;Ans(pos[x])+=1ll*(x-l+1)*(r-x+1);} } inline void Back(){ while(top){ int flag=stk[top].flag; int x=stk[top--].x; if(!flag) --Ans(pos[x]); else if(flag==1){R[L[x]]=x-1;Ans(pos[x])-=x-L[x]+1;} else if(flag==2){L[R[x]]=x+1;Ans(pos[x])-=R[x]-x+1;} else {int l=L[x-1],r=R[x+1];R[l]=x-1;L[r]=x+1;Ans(pos[x])-=1ll*(x-l+1)*(r-x+1);} L[x]=R[x]=0;v[x]=0;--Cnt(pos[x]); } } inline void init(){ lenth=510;tot=(n-1)/lenth+1; for(int i=1;i<=n;++i) pos[i]=(i-1)/lenth+1; for(int i=1;i<=tot;++i) lpos(i)=(i-1)*lenth+1,rpos(i)=i*lenth; rpos(tot)=n; } inline void clear(){ // cout<<"=========== CLEAR =============\n"; top=0;for(int i=1;i<=n;++i) v[i]=L[i]=R[i]=0; for(int i=1;i<=tot;++i) Cnt(i)=Ans(i)=0; } #define mul(x) ((1ll*(x)*(x+1))>>1) inline ll Ask(int l,int r){ int st=pos[l],ed=pos[r],len=0;res=0; if(st==ed){ for(int i=l;i<=r;++i) (v[i])?(res+=++len):(len=0); }else { for(int i=l;i<=rpos(st);++i) (v[i])?(res+=++len):(len=0); for(int i=st+1,l,r;i<ed;++i){ res+=Ans(i);l=lpos(i);r=rpos(i); (v[lpos(i)])&&(res+=1ll*len*(R[l]-l+1)); len=(Cnt(i)==r-l+1)?(len+Cnt(i)):v[r]*(r-L[r]+1); } for(int i=lpos(ed);i<=r;++i) (v[i])?(res+=++len):(len=0); } return res; } }b; void Init(){for(int i=1;i<=m;++i){pos[i]=(i-1)/lenth+1;if(!lp[pos[i]]){lp[pos[i]]=i;rp[pos[i]-1]=i-1;}}rp[tot]=m;} void Work(){ for(int i=1;i<=tot;++i){ b.clear(); int cnt1=0,cnt2=0; for(int j=lp[i];j<=rp[i];++j) (q[j].opt==1)?(vis[q[j].x]=1,c[++cnt1].x=q[j].x,c[cnt1].y=q[j].y,c[cnt1].ti=j):d[++cnt2]=q[j]; for(int j=1;j<=n;++j) {h[j]=0;vis[j]?(vis[j]=0):(d[++cnt2]=Query(3,j,0,a[j],0),0);} cnt=0; for(int j=1;j<=cnt2;++j) ne[++cnt]=h[d[j].z],h[d[j].z]=j; for(int i=1;i<=n;++i){ for(int j=h[i];j;j=ne[j]){ if(d[j].opt==3) b.upd(d[j].x,0); else{ for(int k=1;k<=cnt1;++k) val[c[k].x]=(c[k].ti<d[j].id?(c[k].y):(val[c[k].x]?val[c[k].x]:a[c[k].x])); for(int k=1;k<=cnt1;++k) val[c[k].x]=((val[c[k].x]<=d[j].z&&val[c[k].x])&&(b.upd(c[k].x,1),0)); ans[d[j].id]=b.Ask(d[j].x,d[j].y);b.Back(); } } } for(int j=lp[i];j<=rp[i];++j) a[q[j].x]=(q[j].opt==1)?q[j].y:a[q[j].x]; } } signed main(){ cin>>n>>m; b.init(); lenth=2370;tot=(m-1)/lenth+1; for(int i=1;i<=n;++i) cin>>a[i]; for(int i=1;i<=m;++i){ cin>>q[i].opt>>q[i].x>>q[i].y; (q[i].opt==2)?(cin>>q[i].z,0):0; q[i].id=i; } Init();Work(); for(int i=1;i<=m;++i) if(q[i].opt==2)cout<<ans[i]<<endl; return 0; } ``` ## [星野爱](https://www.luogu.com.cn/problem/P12461) 把图拍扁成一个长为 $2m$ 的序列 $a_{1\dots 2m}$,一个节点 $i$ 管辖的区间是 $[L_i,R_i]$,问题即可转化为: 操作一:$\forall i\in[L_l,R_r],w_{a_i}\gets w_{a_i}+v$。 操作二:求 $\sum\limits_{i=L_l}^{R_r} w_{a_i}$。 套路地,我们对度数考虑根号处理,本题采用带权分块,将节点编号按照 $deg$ 分块使得每一块都度数和都 $\le B$,$deg_x>B$ 的 $x$ 单独分一块,注意去掉零度点否则复杂度会爆炸,不过你按 $deg+1$ 分块就没这个烦恼了。 然后和常规分块还有一个差别就是如果一个块被完全包含就直接将其看作整块,不像普通分块默认把边角块当作散的。 思考修改对询问的贡献: 1. 散块对散块,直接暴力。 2. 整块对区间,存 $f_{i,j}$ 表示第 $i$ 个块修改对前 $j$ 个点的贡献。 3. 散块对整块:跟整块对散块本质相同。 $O(n\sqrt n)$ 的空间,相当之寄。考虑逐块处理,具体地,直接枚举整块,对于每一个整块算 $f_{i,j}$。 ```cpp #include<bits/stdc++.h> #define ll unsigned long long using namespace std; #include<bits/stdc++.h> using namespace std; namespace Fread { const int SIZE=1<<21;char buf[SIZE],*S,*T; inline char getchar() {if(S==T){T=(S=buf)+fread(buf,1,SIZE,stdin);if(S==T)return '\n';}return *S++;} } namespace Fwrite { const int SIZE=1<<21; char buf[SIZE],*S=buf,*T=buf+SIZE; inline void flush(){fwrite(buf,1,S-buf,stdout);S=buf;} inline void putchar(char c){*S++=c;if(S==T)flush();} struct POPOSSIBLE{~POPOSSIBLE(){flush();}}ztr; } #define getchar Fread :: getchar #define putchar Fwrite :: putchar namespace Fastio{ struct Reader{ template<typename T> Reader& operator >> (T& x) { char c=getchar();T f=1; while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}x=0; while(c>='0'&&c<='9'){x=x*10+(c-'0');c=getchar();}x*=f; return *this; } Reader(){} }cin; struct Writer{ template<typename T> Writer& operator << (T x) { if(x==0){putchar('0');return *this;} if(x<0){putchar('-');x=-x;} static int sta[45];int top=0; while(x){sta[++top]=x%10;x/=10;} while(top){putchar(sta[top]+'0');--top;} return *this; } Writer& operator << (char c) {putchar(c);return *this;} Writer(){} }cout; } #define cin Fastio :: cin #define cout Fastio :: cout const int N=4e5+10; struct Query{ int op,l,r,v,zl,zr; Query(int op=0,int l=0,int r=0,int v=0): op(op),l(l),r(r),v(v){;} }q[N]; int n,m,Q,lenth,pos[N],lpos[N],rpos[N],tot,ct[N],a[N],L[N],R[N],cnt; vector < int > v[N];ll ans[N],val[N],f[N],w,now,VAL; inline void add(int x,int y){v[x].emplace_back(y);v[y].emplace_back(x);} inline void SetBlock(int l,int r){ // cout << tot+1 << ' ' << l << ' ' << r << "--\n"; ++tot;for(int i=l;i<=r;++i) pos[i]=tot; lpos[tot]=l;rpos[tot]=r; } inline void Corner(int id){//处理散块 int op=q[id].op,l=q[id].l,r=q[id].r,vl=q[id].v; int st=pos[l],ed=pos[r]; // cout << st << ' ' << ed << ' ' << l << ' ' << r << "\n"; q[id].zl=st+(l!=lpos[st]); q[id].zr=ed-(r!=rpos[ed]); // cout<<"GCX,are you ok? "<<id<<' '<<q[id].zl<<' '<<q[id].zr<<'\n'; if(l==lpos[st]&&r==rpos[ed]) return ; if(op==1){ if(st==ed){ for(int i=l;i<=r;++i) for(int j:v[i]) val[j]+=vl; }else { if(l!=lpos[st]) for(int i=l;i<=rpos[st];++i) for(int j:v[i]) val[j]+=vl; if(r!=rpos[ed]) for(int i=lpos[ed];i<=r;++i) for(int j:v[i]) val[j]+=vl; } }else { if(st==ed){ for(int i=l;i<=r;++i) for(int j:v[i]) ans[id]+=val[j]; }else { if(l!=lpos[st]) for(int i=l;i<=rpos[st];++i) for(int j:v[i]) ans[id]+=val[j]; if(r!=rpos[ed]) for(int i=lpos[ed];i<=r;++i) for(int j:v[i]) ans[id]+=val[j]; } // cout << ans[id] << "---\n"; } } inline void init(int id){ for(int j=L[lpos[id]];j<=R[rpos[id]];++j) ++ct[a[j]]; for(int i=1;i<=n;++i){ for(int j=L[i];j<=R[i];++j) f[i]+=ct[a[j]]; f[i]+=f[i-1]; } } inline void clear(){VAL=w=0;for(int i=1;i<=n;++i) ct[i]=f[i]=val[i]=0;} inline void Solve1(int p){//整块对区间 int op=q[p].op,l=q[p].l,r=q[p].r,v=q[p].v; if(op==1)(l<=lpos[now]&&rpos[now]<=r)&&(w+=v); else ans[p]+=(f[r]-f[l-1])*w; } inline void Solve2(int p){//散块对整块 int op=q[p].op,l=q[p].l,r=q[p].r,v=q[p].v; if(op==1){ if(q[p].zl<=q[p].zr){ int sl=l,sr=rpos[q[p].zl-1];VAL+=(f[sr]-f[sl-1])*v; sl=lpos[q[p].zr+1];sr=r;VAL+=(f[sr]-f[sl-1])*v; }else VAL+=(f[r]-f[l-1])*v; }else (l<=lpos[now]&&rpos[now]<=r)&&(ans[p]+=VAL); } inline void Deal(int id){for(int i=1;i<=Q;++i){Solve1(i);Solve2(i);}} signed main(){ cin>>n>>m>>Q;for(int i=1,x,y;i<=m;++i){cin>>x>>y;add(x,y);} for(int i=1;i<=Q;++i){ cin>>q[i].op>>q[i].l>>q[i].r; if(q[i].op==1) cin>>q[i].v; } for(int i=1;i<=n;++i){ L[i]=cnt+1; for(int j:v[i]) a[++cnt]=j; R[i]=cnt; } lenth=sqrt(3*m)*1.5;int s=0,las=1; for(int i=1;i<=n;++i){//带权分块 int sz=v[i].size()+1; if(s+sz>lenth){ s=0; if(las==i) SetBlock(i,i),las=i+1; else {SetBlock(las,i-1);las=i;--i;continue;} }else s+=sz; } if(las<=n) SetBlock(las,n); lpos[tot+1]=n+1;for(int i=1;i<=Q;++i) Corner(i); for(int i=1;i<=tot;++i){now=i;init(i);Deal(i);clear();} for(int i=1;i<=Q;++i) if(q[i].op==2) cout<<ans[i]<<'\n'; return 0; } ``` ## [TEST 68](https://www.luogu.com.cn/problem/P8511) 首先求出全局的答案,假设这个答案由节点 $x,y$ 贡献,那么只有子树包含了 $x$ 或 $y$ 的节点被删掉时可能会让答案变化,也就是我们只需要考察 $x$ 和 $y$ 到根路径的并,进一步地,可以分别考察 $x,y$ 到根路径上的节点的答案。 明显可以暴力删除和撤销,时间复杂度是 $O(n\log V)$ 的。 ## [超人机械](https://www.luogu.com.cn/problem/P9068) dx 在四川省集提到过。 用 $x_i,y_i$ 表示 $i$ 第一次和最后一次出现的位置,那么两个数 $i>j$ 之间有贡献当且仅当 $x_i<y_j$。修改明显只影响 $O(1)$ 个 $x,y$。 算上时间轴很明显是一个三维偏序的形式,离线下来直接套用 CDQ 分治可以做到 $O(n\log^2 n)$。 ## [暴力写挂](https://www.luogu.com.cn/problem/P4565) 感觉如果你做过河童重工一类题可能会比较容易理解。 $dep_x+dep_y-dep_{LCA(x,y)}=\frac 12(dep_x+dep_y+dis(x,y))$。 我们思考这样的转化改变了什么,$LCA$ 是定义在有根树上的,而 $dis(x,y)$ 可以定义在无根树上,限制被我们放松了。 考虑暴力的边分治,将分治中心左边的点在第二棵树上染成红色,右边的点染成蓝色。定义 $p_x$ 为 $x$ 到分治中心的一个端点的最短距离,再定义 $w_x=p_x+dep_x$,那么实际上我们需要在第二棵树上找到一对异色点 $x,y$,最大化 $\frac 12(w_x+w_y+val)+dep_{LCA(x,y)}$。 直接建虚树 DP 一下就可以了,是 $O(n\log^2 n)$ 的。 考虑建出边分树,每次按 dfs 序归并左右连通块然后单调栈建虚树好像就可以做到 $O(n\log n)$,不过如果你写得好看可能可以 $O(n\log ^2 n)$ 冲过去。