更高更妙の数据结构专练

为什么更高更妙呢?因为时间复杂度一般有除了 \(\log\) 以外的东西。


A. [SHOI2006] 作业

直接根号分治,对于 \(Y\le\sqrt V\) 的情况直接记录每个集合的答案,否则记录每个集合的数,绕环找后继即可。时间复杂度可以做到 \(O(n\sqrt V)\),不过 \(O(n\sqrt V\log n)\) 也能过。

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5,k=300;
int n,nx[N],fl[1005],as[1005];
multiset<int>st;
inline void chg(int x,int l,int r,int v){
	if(fl[x]){
		for(int i=x*k;i>x*k-k;i--) nx[i]=fl[x];
		fl[x]=0;
	}
	for(int i=l;i<=r;i++) nx[i]=v;
}
inline void add(int l,int r,int v){
	if((l-1)/k==(r-1)/k){
		chg((l-1)/k+1,l,r,v);
		return;
	}
	chg((l-1)/k+1,l,(l-1)/k*k+k,v);
	chg((r-1)/k+1,(r-1)/k*k+1,r,v);
	for(int i=(l-1)/k+2;i<=(r-1)/k;i++) fl[i]=v;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	st.insert(0),st.insert(3e5+1);
	for(int i=1;i<=1000;i++) as[i]=1e9;
	for(int i=1;i<=3e5;i++) nx[i]=1e9;
	while(n--){
		char c;
		int x;
		cin>>c>>x;
		if(c=='B'){
			if(x>1000){
				int ans=(fl[1]?fl[1]:nx[1]);
				for(int i=x;i<=3e5;i+=x)
					ans=min(ans,(fl[(i-1)/k+1]?fl[(i-1)/k+1]:nx[i])-i);
				cout<<ans<<"\n";
			}
			else cout<<as[x]<<"\n";
		}
		else{
			int num=*--st.lower_bound(x);
			st.insert(x),add(num+1,x,x);
			for(int i=1;i<=1000;i++) as[i]=min(as[i],x%i);
		}
	}
	return 0;
}

B. [CF1882G2] Magic Triples (Hard Version)

由于上一题,所以下意识想到直接对 \(b\) 做根号分治。对于 \(b\le\sqrt[3]V\) 是容易的,时间复杂度为 \(O(n\sqrt[3]V)\);但是发现解决 \(\sqrt[3]V<b\le\sqrt V\) 这一部分的时间复杂度为 \(O(\min(n\sqrt[2]V,V^\frac 56))\),由于是多测,所以这个时间复杂度肯定是过不了的。考虑对于 \(n\) 较小的情况,写 \(O(n^2)\) 的暴力,这样就可以迷惑地 \(A\) 了。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int T,n,a[N];
long long ans;
struct hash_map{
	static const int modd=1145141;
	struct lxl{
		int u,v,nx;
	}e[N];
	int cnt,h[modd];
	inline void clear(){
		while(cnt) h[e[cnt--].u%modd]=0;
	}
	inline void add(int x,int v){
		int hx=x%modd;
		for(int i=h[hx];i;i=e[i].nx)
			if(e[i].u==x){
				e[i].v+=v;
				return;
			}
		e[++cnt]={x,v,h[hx]},h[hx]=cnt;
	}
	inline int num(int x){
		int hx=x%modd;
		for(int i=h[hx];i;i=e[i].nx)
			if(e[i].u==x) return e[i].v;
		return 0;
	}
}mp;
inline void solve(){
	cin>>n;
	mp.clear();
	for(int i=1;i<=n;i++)
		cin>>a[i],mp.add(a[i],1);
	sort(a+1,a+n+1),ans=0;
	n=unique(a+1,a+n+1)-a-1;
	for(int i=1;i<=n;i++)
		ans+=1ll*mp.num(a[i])*(mp.num(a[i])-1)*(mp.num(a[i])-2);
	if(n<=5000){
		for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++)
			if(a[j]%a[i]==0&&1e9/a[j]*a[i]>=a[j])
				ans+=1ll*mp.num(a[i])*mp.num(a[j])*mp.num(a[j]/a[i]*a[j]);
		cout<<ans<<"\n";
		return;
	}
	for(int i=2;i<=32000;i++)
		for(int j=1;j<=n&&a[j]<=1e9/i/i;j++)
			ans+=1ll*mp.num(a[j])*mp.num(a[j]*i)*mp.num(a[j]*i*i);
	cout<<ans<<"\n";
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>T;
	while(T--) solve();
	return 0;
}

C. [Ynoi Easy Round 2015] 此时此刻的光辉

之前做过。

显然的想法是直接质因数分解,然后由于 \(\le V=10^9\) 的数最多只有 \(10\) 个质因子,所以只有 \(10n\) 个质数需要考虑。离散化后跑莫队就可以拿到 \(O(10n\sqrt n)\) 的时间复杂度了。由于质因数分解时间太久了,考虑将 \(\le 1000\) 的质数全部拉出来计算前缀和,这样就只需要维护 \(>1000\) 的质数了。由于只剩两个,所以分解质因数速度大大提高。

上古码风,懒得改了。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,p=19260817;
template<class P,class Q>
class HashTable{
	static const int mod=1e6+7,L=mod+10;
	int head[L],nxt[N*2],siz;P val[N*2];Q sav[N*2];
	public:
	inline void clear() {
		for(int i=1;i<=siz;i++){
			head[val[i]%mod]=0,nxt[i]=val[i]=0;
			Q tmp;swap(sav[i],tmp);
		}siz=0;
	}inline void insert(P y){
		val[++siz]=y;
		nxt[siz]=head[y%mod];
		head[y%mod]=siz;
	}inline Q &operator[](const P y) {
		int x=y%mod;
		for(int i=head[x];i;i=nxt[i])
			if(val[i]==y)
				return sav[i];
		insert(y);
		return sav[siz];
	}inline bool count(const P y) {
		int x=y%mod;
		for(int i=head[x];i;i=nxt[i])
			if(val[i]==y) return 1;
		return 0;
	}
};HashTable<int,int>mp;
int n,m,a[N],pc[205],vs[1005],tg[205],tp,as;
vector<int>cc[N];vector<pair<int,int> >pr[N];
struct que{int l,r,id;}qu[N];int ans[N],inv[N*40];
int cmp(que x,que y){
	if(x.l/300!=y.l/300) return x.l/300<y.l/300;
	return (x.l/300%2?x.r>y.r:x.r<y.r);
}namespace rho{
	int mr[8]={2,3,5,7,11,13,17,19};
	int qpow(int x,int y,int c){
		int re=1;
		while(y){
			if(y&1) re=1ll*re*x%c;
			x=1ll*x*x%c,y>>=1;
		}return re;
	}int check(int x,int md){
		int c=md-1,mid=qpow(x,c,md);
		if(mid!=1) return 0;
		while(!(c&1)&&mid==1)
			c>>=1,mid=qpow(x,c,md);
		return (mid==1||mid==md-1);
	}int miller_rabin(int x){
		if(x<2) return 0;
		if(x<=19){
			for(int i=0;i<8;i++)
				if(mr[i]==x) return 1;
			return 0;
		}for(int i=0;i<8;i++)
			if(!check(mr[i],x)) return 0;
		return 1;
	}int gcd(int x,int y){
		return (!y)?x:gcd(y,x%y);
	}int pr(int x){
		int s=0,t=0,val=1;
		int c=rand()%(x-1)+1;
		for(int gl=1;;gl*=2,s=t,val=1){
			for(int st=1;st<=gl;st++){
				t=(1ll*t*t+c)%x;
				val=1ll*val*abs(t-s)%x;
				if(st%127==0){
					int d=gcd(val,x);
					if(d>1) return d;
				}
			}int d=gcd(val,x);
			if(d>1) return d;
		}
	}void pol_rho(int x,vector<int>&ve){
		if(x<2) return;
		if(miller_rabin(x))
			return ve.push_back(x),void();
		int p=x;while(p>=x) p=pr(x);
		while(x%p==0) x/=p,pol_rho(p,ve);
		pol_rho(x,ve);
	}
}void add(int x){
	for(auto [y,cc]:pr[x]) tg[y]+=cc,as=1ll*as*inv[tg[y]-cc+1]%p*(tg[y]+1)%p;
	for(auto y:cc[x]) mp[y]++,as=1ll*as*inv[mp[y]]%p*(mp[y]+1)%p;
}void del(int x){
	for(auto [y,cc]:pr[x]) as=1ll*as*inv[tg[y]+1]%p*(tg[y]-cc+1)%p,tg[y]-=cc;
	for(auto y:cc[x]) as=1ll*as*inv[mp[y]+1]%p*mp[y]%p,mp[y]--;
}signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0),cin>>n>>m,inv[1]=1;
	for(int i=2;i<=4e6;i++) inv[i]=1ll*(p-p/i)*inv[p%i]%p;
	for(int i=2;i<=1e3;i++) if(!vs[i]){
		for(int j=i*2;j<=1e3;j+=i) vs[j]=1;pc[++tp]=i;
	}for(int i=1;i<=n;i++){
		int a;cin>>a;
		for(int j=1;j<=tp&&a>1;j++){
			if(a%pc[j]) continue;int num=0;
			while(a%pc[j]==0) num++,a/=pc[j];
			pr[i].push_back({j,num});
		}rho::pol_rho(a,cc[i]);
	}for(int i=1;i<=m;i++) cin>>qu[i].l>>qu[i].r,qu[i].id=i;
	sort(qu+1,qu+m+1,cmp),as=1;
	for(int i=1,l=1,r=0;i<=m;i++){
		while(r<qu[i].r) add(++r);
		while(l>qu[i].l) add(--l);
		while(r>qu[i].r) del(r--);
		while(l<qu[i].l) del(l++);
		ans[qu[i].id]=as;
	}for(int i=1;i<=m;i++) cout<<ans[i]<<"\n";
	return 0;
}

D. [Ynoi2013] 无力回天 NOI2017

首先求并用容斥原理搞成求交。

有两种思路。第一种是计算出每个集合都有哪些数,然后求交。可以使用 \(bitset\) 随意优化到 \(O(\frac{d^2}w)\),但是 \(A\) 不了。其中 \(d\) 表示出现的元素种类数。

第二种是直接计算 \(dp_{x,y}\) 表示他们的交有多大。每次给 \(x\) 加入一个数时考虑 \(y\) 有没有,有的话 \(dp_{x,y}\) 加一。时间复杂度 \(O(n^2)\),但是对于每个数,若它出现 \(c\) 次,时间复杂度就为 \(O(c^2)\)

我们发现一个希望每个元素出现次数尽量小,一个希望元素尽量少,考虑根号分治,将时间复杂度转化为 \(O(n\sqrt{\frac nw})\),佐以卡常即可通过。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
char buf[1<<20],*p1,*p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++)
template<typename T>
inline void read(T &x){
	x=0;
	char c=getchar();
	bool fl=0;
	while(c>57||c<48) fl=(c=='-'),c=getchar();
	while(c>=48&&c<=57)
		x=(x<<1)+(x<<3)+c-48,c=getchar();
	x=(fl?-x:x);
}
template<typename T>
inline void write(T x){
	if(x<0) x=-x,putchar('-');
	if(x>9) write(x/10);
	putchar(x%10+48);
}
const int N=1e6+5,M=3505;
int n,cnt,df[M],num[N],vs[N],nm[N];
int anna[N],*c[N],*ed;
bitset<M>bt[N];
struct que{
	int opt,x,y;
}qu[N];
struct hash_map{
	static const int modd=5074157;
	struct lxl{
		int x,y;
        int v,nx;
	}e[N];
	int cnt,h[modd];
	inline void mk(int x,int y){
		int hx=(1ll*x*n+y)%modd;
		e[++cnt]={x,y,0,h[hx]},h[hx]=cnt;
	}
	inline void add(int x,int y){
		int hx=(1ll*x*n+y)%modd;
		for(int i=h[hx];i;i=e[i].nx)
			if(e[i].x==x&&e[i].y==y){
                e[i].v++;
                return;
            }
        return;
	}
    inline int num(int x,int y){
		int hx=(1ll*x*n+y)%modd;
		for(int i=h[hx];i;i=e[i].nx)
			if(e[i].x==x&&e[i].y==y) return e[i].v;
		return 0;
	}
}mp;
int main(){
	read(n);
	for(int i=1;i<=n;i++){
		read(qu[i].opt),read(qu[i].x),read(qu[i].y);
		if(qu[i].opt==1) num[qu[i].y]++;
		else{
			if(qu[i].x>qu[i].y) swap(qu[i].x,qu[i].y);
			mp.mk(qu[i].x,qu[i].y);
		}
	}
	ed=anna;
	for(int i=1;i<=n;i++){
		if(num[i]>100) df[num[i]=++cnt]=i,vs[i]=1;
		else c[i]=ed,ed+=num[i],num[i]=0;
	}
	for(int i=1;i<=n;i++){
		if(qu[i].opt==1){
			if(!vs[qu[i].y]){
				c[qu[i].y][++num[qu[i].y]]=qu[i].x;
				for(int j=1;j<=num[qu[i].y];j++){
					int x=c[qu[i].y][j],y=qu[i].x;
					if(x>y) swap(x,y);
					mp.add(x,y);
				}
			}
			else bt[qu[i].x][num[qu[i].y]]=1;
			nm[qu[i].x]++;
		}
		else{
			int ans=(bt[qu[i].x]&bt[qu[i].y]).count();
			write(nm[qu[i].x]+nm[qu[i].y]-ans-mp.num(qu[i].x,qu[i].y));
			putchar('\n');
		}
	}
	return 0;
}

E. [Cnoi2019] 数字游戏

考虑到四元组过于麻烦,必定要通过两个数据结构分别解决。那就只有树套树或莫队+分块能解决这类问题。本题使用后者。

实际上这道题莫队维护值域会更方便。发现莫队将本题转化为对于一个 \(0/1\) 序列做单点修改和区间查联通块大小的二次方之和。使用线段树一类的 \(O(\log n)-O(\log n)\) 的算法显然比较容易。考虑分块似乎并不方便,于是改用类块状链表状物进行计算。快速查询一个链表大小则用小常数的可撤销并查集即可。时间复杂度 \(O(n\sqrt{n\log n})\)

#include<bits/stdc++.h>
using namespace std; 
const int N=2e5+5;
int n,q,a[N],ps[N],kl=500;
long long ans[N],num[N];
int fa[N],sz[N],fl[N],tp;
struct rt{
	int id;
	long long nm;
	int xc,yc;
}st[N];
inline long long numd(int x){
	return x*(x+1ll)/2;
}
inline int find(int x){
	return x==fa[x]?x:find(fa[x]);
}
inline void unite(int x,long long &re,int fl=0){
	int y=find(x-1);
	x=find(x);
	long long flg=re;
	re-=numd(sz[x])+numd(sz[y]);
	if(sz[x]<sz[y]) swap(x,y);
	if(fl) st[++tp]={0,0,x,y};
	sz[x]+=sz[y],fa[y]=x;
	re+=numd(sz[x]);
}
struct que{
	int l,r,x,y,id;
}qu[N];
inline bool cmp(que x,que y){
	return x.x/kl!=y.x/kl?x.x<y.x:x.y<y.y;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i],ps[a[i]]=i;
	for(int i=1;i<=q;i++)
		cin>>qu[i].l>>qu[i].r>>qu[i].x>>qu[i].y,qu[i].id=i;
	sort(qu+1,qu+q+1,cmp);
	for(int j=1;j<=n;j++) sz[fa[j]=j]=1,fl[j]=0;
	for(int i=1,cc=0,r=kl-1;i<=q;i++){
		if(cc!=qu[i].x/kl){
			cc=qu[i].x/kl,r=cc*kl+kl-1;
			for(int j=0;j<=500;j++) num[j]=0;
			for(int j=1;j<=n;j++) sz[fa[j]=j]=1,fl[j]=0;
		}
		if(qu[i].x/kl==qu[i].y/kl){
			for(int j=qu[i].x;j<=qu[i].y;j++)
				if(ps[j]>=qu[i].l&&ps[j]<=qu[i].r){
					fl[ps[j]]=1,ans[qu[i].id]++;
					if(fl[ps[j]-1]) unite(ps[j],ans[qu[i].id]);
					if(fl[ps[j]+1]) unite(ps[j]+1,ans[qu[i].id]);
				}
			for(int j=qu[i].x;j<=qu[i].y;j++)
				fl[ps[j]]=0,sz[fa[ps[j]]=ps[j]]=1;
			continue;
		}
		while(r<qu[i].y){
			num[ps[++r]/kl]++,fl[ps[r]]=1;
			if(ps[r]%kl&&fl[ps[r]-1]) unite(ps[r],num[ps[r]/kl]);
			if((ps[r]+1)%kl&&fl[ps[r]+1]) unite(ps[r]+1,num[ps[r]/kl]);
		}
		for(int j=qu[i].x;j<cc*kl+kl;j++){
			st[++tp]={ps[j],num[ps[j]/kl],0,0};
			fl[ps[j]]=1,num[ps[j]/kl]++;
			if(ps[j]%kl&&fl[ps[j]-1]) unite(ps[j],num[ps[j]/kl],1);
			if((ps[j]+1)%kl&&fl[ps[j]+1]) unite(ps[j]+1,num[ps[j]/kl],1);
		}
		int numc=fl[qu[i].l];
		if(qu[i].l/kl==qu[i].r/kl){
			for(int j=qu[i].l+1;j<=qu[i].r;j++){
				if(fl[j]) numc++;
				else ans[qu[i].id]+=numd(numc),numc=0;
			}
			ans[qu[i].id]+=numd(numc);
		}
		else{
			for(int j=qu[i].l+1;j<qu[i].l/kl*kl+kl;j++){
				if(fl[j]) numc++;
				else ans[qu[i].id]+=numd(numc),numc=0;
			}
			for(int j=qu[i].l/kl+1;j<qu[i].r/kl;j++){
				if(sz[find(j*kl)]*fl[j*kl]==kl) numc+=kl;
				else{
					ans[qu[i].id]+=num[j]-numd(fl[j*kl]*sz[find(j*kl)]);
					ans[qu[i].id]+=numd(numc+fl[j*kl]*sz[find(j*kl)]);
					ans[qu[i].id]-=numd(numc=fl[j*kl+kl-1]*sz[find(j*kl+kl-1)]);
				}
			}
			for(int j=qu[i].r/kl*kl;j<=qu[i].r;j++){
				if(fl[j]) numc++;
				else ans[qu[i].id]+=numd(numc),numc=0;
			}
			ans[qu[i].id]+=numd(numc);
		}
		while(tp){
			num[st[tp].id/kl]=st[tp].nm;
			sz[st[tp].xc]-=sz[st[tp].yc];
			fa[st[tp].yc]=st[tp].yc;
			fl[st[tp--].id]=0;
		}
	}
	for(int i=1;i<=q;i++) cout<<ans[i]<<"\n";
	return 0;
}

F. [Ynoi2013] 文化课

我当时为什么会点开这道题……

考虑乘法操作连成一个联通块,所以线段树维护的信息和上一道题差不多一个样。假如没有修改,就是和上一道题的线段树差不多;假如只修改符号,就对于每个区间记录全乘和全加的值;假如同时修改数字序列,那就对每个区间维护一个多项式意义的信息,修改时带入计算新信息即可。

由于经典结论,长为 \(n\) 的算式组成的多项式项数为 \(O(\sqrt n)\)。多项式合并的时候使用归并排序,时间复杂度就可以做到 \(O(n\sqrt n)\)。由于要用快速幂所以时间复杂度多一个 \(\log\)(不过好像可以被优化掉)。

#include<bits/stdc++.h>
#define int long long
using namespace std;
char buf[1<<20],*p1,*p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++)
template<typename T>
inline void read(T &x){
	x=0;
	char c=getchar();
	bool fl=0;
	while(c>57||c<48) fl=(c=='-'),c=getchar();
	while(c>=48&&c<=57)
		x=(x<<1)+(x<<3)+c-48,c=getchar();
	x=(fl?-x:x);
}
template<typename T>
inline void write(T x){
	if(x>9) write(x/10);
	putchar(x%10+48);
}
const int N=1e5+5,p=1e9+7;
inline int qpow(int x,int y){
	int re=1;
	while(y){
		if(y&1) re=re*x%p;
		x=x*x%p,y>>=1;
	}
	return re;
}
int n,m,ac[N],pc[N],lv[N<<1];
int tms[N<<1],sum[N<<1],fla[N<<1],flp[N<<1];
struct seg{
	int fl,pr,sf,ans;
}sg[N<<1];
struct dat{
	int a,b;
};
struct segdx{
	int fl,pr,sf;
	vector<dat>ans;
}sgd[N<<1];
inline seg operator+(seg x,seg y){
	if(x.fl&&y.fl)
		return {1,x.pr*y.pr%p,x.sf*y.sf%p,0};
	if(x.fl) return {0,x.pr*y.pr%p,y.sf,y.ans};
	if(y.fl) return {0,x.pr,x.sf*y.sf%p,x.ans};
	return {0,x.pr,y.sf,(x.ans+y.ans+x.sf*y.pr)%p};
}
inline segdx operator+(segdx x,segdx y){
	if(x.fl&&y.fl)
		return {1,x.pr+y.pr,x.sf+y.sf,{}};
	if(x.fl) return {0,x.pr+y.pr,y.sf,y.ans};
	if(y.fl) return {0,x.pr,x.sf+y.sf,x.ans};
	segdx re={0,x.pr,y.sf,{}};
	int lid=0,rid=0,fl=0;
	while(lid<x.ans.size()||rid<y.ans.size()||!fl){
		dat ad=(lid==x.ans.size()?(dat){(int)1e18,0}:x.ans[lid]);
		int flx=1,fly=0,flf=0;
		if(rid<y.ans.size()){
			if(ad.a>y.ans[rid].a) ad=y.ans[rid],flx=0,fly=1;
			else if(ad.a==y.ans[rid].a) ad.b+=y.ans[rid].b,fly=1;
		}
		if(!fl){
			if(ad.a>x.sf+y.pr) ad={x.sf+y.pr,1},flx=fly=0,flf=1;
			else if(ad.a==x.sf+y.pr) ad.b++,flf=1;
		}
		re.ans.push_back(ad),lid+=flx,rid+=fly,fl+=flf;
	}
	return re;
}
inline seg trans(int x,segdx c){
	int re=0;
	for(dat y:c.ans) re=(re+y.b*qpow(x,y.a))%p;
	return {c.fl,qpow(x,c.pr),qpow(x,c.sf),re};
}
inline void push_up(int x,int mid){
	sum[x]=(sum[mid<<1]+sum[mid<<1|1])%p;
	tms[x]=tms[mid<<1]*tms[mid<<1|1]%p;
	sg[x]=sg[mid<<1]+sg[mid<<1|1];
	sgd[x]=sgd[mid<<1]+sgd[mid<<1|1];
	lv[x]=lv[mid<<1];
}
inline void build(int x,int l,int r){
	if(l==r){
		sum[x]=tms[x]=lv[x]=ac[l];
		sg[x]={pc[l],ac[l],(pc[l]?ac[l]:1),0};
		sgd[x]={pc[l],1,flp[x]=pc[l],{}};
		return;
	}
	int mid=(l+r)>>1;
	build(mid<<1,l,mid);
	build(mid<<1|1,mid+1,r);
	flp[x]=-1,push_up(x,mid);
}
inline void downa(int x,int v,int len){
	lv[x]=fla[x]=v,sg[x]=trans(v,sgd[x]);
	tms[x]=qpow(v,len),sum[x]=len*v%p;
}
inline void downp(int x,int v,int len){
	if(!v){
		if(len<2) sgd[x]={0,1,0,{}};
		else sgd[x]={0,1,0,{{1,len-1}}};
		flp[x]=v,sg[x]={0,lv[x],1,(sum[x]-lv[x]+p)%p};
	}
	else{
		flp[x]=v,sgd[x]={1,len,len,{}};
		sg[x]={1,tms[x],tms[x],0};
	}
}
inline void push_down(int x,int l,int mid,int r){
	if(fla[x]) downa(mid<<1,fla[x],mid-l+1),downa(mid<<1|1,fla[x],r-mid),fla[x]=0;
	if(flp[x]>=0) downp(mid<<1,flp[x],mid-l+1),downp(mid<<1|1,flp[x],r-mid),flp[x]=-1;
}
inline void chga(int x,int l,int r,int L,int R,int v){
	if(L<=l&&r<=R) return downa(x,v,r-l+1);
	int mid=(l+r)>>1;
	push_down(x,l,mid,r);
	if(L<=mid) chga(mid<<1,l,mid,L,R,v);
	if(R>mid) chga(mid<<1|1,mid+1,r,L,R,v);
	push_up(x,mid);
}
inline void chgp(int x,int l,int r,int L,int R,int v){
	if(L<=l&&r<=R) return downp(x,v,r-l+1);
	int mid=(l+r)>>1;
	push_down(x,l,mid,r);
	if(L<=mid) chgp(mid<<1,l,mid,L,R,v);
	if(R>mid) chgp(mid<<1|1,mid+1,r,L,R,v);
	push_up(x,mid);
}
inline void answ(int x,int l,int r,int L,int R,seg &c){
	if(L<=l&&r<=R){
		c=c+sg[x];
		return;
	}
	int mid=(l+r)>>1;
	push_down(x,l,mid,r);
	if(L<=mid) answ(mid<<1,l,mid,L,R,c);
	if(R>mid) answ(mid<<1|1,mid+1,r,L,R,c);
}
inline int num(int x,int l,int r,int k){
	if(flp[x]>=0) return !flp[x];
	int mid=(l+r)>>1;
	if(k<=mid) return num(mid<<1,l,mid,k);
	return num(mid<<1|1,mid+1,r,k);
}
signed main(){
	read(n),read(m);
	for(int i=1;i<=n;i++) read(ac[i]),ac[i]%=p;
	for(int i=1;i<n;i++) read(pc[i]);
	build(1,1,n);
	while(m--){
		int op,l,r,x;
		read(op),read(l),read(r);
		if(op<2) read(x),chga(1,1,n,l,r,x%p);
		else if(op==2) read(x),chgp(1,1,n,l,r,x);
		else{
			seg c={0,0,1,0};
			answ(1,1,n,l,r,c);
			write((c.ans+c.sf-num(1,1,n,r))%p);
			putchar('\n');
		}
	}
	return 0;
}

G. [KTSC2020 R1] 穿越

\(A,B\) 固定的话这题顶多就是个水蓝,直接线段树优化 \(dp\) 即可。考虑到一个可能的答案一定能被表示成 \(xA+yB\) 的形式(其中 \(x,y\) 为正整数),那么假如我们可以知道所有可能的 \((x,y)\),我们就只需要暴力便利这些点即可。容易发现这些点只有在左下凸壳的点才有用,问题转化为求解左下凸壳。我们直接把凸壳当成原先线段树中的变量,那么原先 \(O(n\log n)\) 线段树优化 \(dp\) 就需要再乘上凸壳的点数。由于整点凸壳舍去共线点后点数量级为 \(O(n^\frac 23)\),所以预处理时间复杂度为 \(O(n^\frac 53\log n)\)。查询时可以将凸壳拎出来跑二分,总时间复杂度即为 \(O((n^\frac 53+q)\log n)\)

#include<vector>
void init(int N, int M,std::vector<int>Y1,std::vector<int>Y2);
long long minimize(int A,int B);
#include<bits/stdc++.h>
#define conh vector<node>
using namespace std;
const int N=10005;
int n,ad[N<<3];
struct node{
	int x,y;
};
inline bool operator==(node x,node y){
	return x.x==y.x&&x.y==y.y;
}
inline bool check(node x,node y,node z){
	return 1ll*(y.y-x.y)*(z.x-y.x)>=1ll*(z.y-y.y)*(y.x-x.x);
}
conh con[N<<3],flg[N<<3],tmp;
inline void addc(node ad){
	int num=tmp.size();
	if(num&&(tmp[num-1].y<=ad.y||tmp[num-1].x==ad.x)) return;
	while(num>1&&check(tmp[num-2],tmp[num-1],ad))
		tmp.pop_back(),num--;
	
	tmp.push_back(ad);
}
inline void merge(conh &x,conh &y,conh &z){
	conh().swap(tmp);
	for(int i=0,j=0;i<x.size()||j<y.size();){
		if(i==x.size()) addc(y[j++]);
		else if(j==y.size()) addc(x[i++]);
		else if(x[i].x<y[j].x||(x[i].x==y[j].x&&x[i].y<y[j].y)) addc(x[i++]);
		else addc(y[j++]);
	}
	swap(z,tmp);
}
inline void add(conh &cc,int xad,int yad){
	for(int i=cc.size()-1;~i;i--) cc[i].x+=xad,cc[i].y+=yad;
}
inline void build(int x,int l,int r){
	con[x].push_back({0,0});
	if(l==r) return;
	int mid=(l+r)>>1;
	build(x<<1,l,mid);
	build(x<<1|1,mid+1,r);
}
inline void downf(int x,conh &v){
	merge(flg[x],v,flg[x]),merge(con[x],v,con[x]);
}
inline void downa(int x,int v){
	add(flg[x],0,v),add(con[x],0,v),ad[x]+=v;
}
inline void push_down(int x){
	if(ad[x]) downa(x<<1,ad[x]),downa(x<<1|1,ad[x]),ad[x]=0;
	if(flg[x].size()) downf(x<<1,flg[x]),downf(x<<1|1,flg[x]),conh().swap(flg[x]);
}
inline void chg(int x,int l,int r,int L,int R){
	if(L<=l&&r<=R) return downa(x,1);
	int mid=(l+r)>>1;
	push_down(x);
	if(L<=mid) chg(x<<1,l,mid,L,R);
	if(R>mid) chg(x<<1|1,mid+1,r,L,R);
	merge(con[x<<1],con[x<<1|1],con[x]);
}
inline long long answ(node x,int A,int B){
	return 1ll*x.x*A+1ll*x.y*B;
}
int k,e[N<<1];
unordered_map<int,int>mp;
void init(int N,int M,vector<int>Y1,vector<int>Y2){
	e[++k]=1;
	for(int i:Y1) e[++k]=i+1;
	for(int i:Y2) e[++k]=i+2;
	sort(e+1,e+k+1),k=unique(e+1,e+k+1)-e-1;
	for(int i=1;i<=k;i++) mp[e[i]]=i;
	if(e[k]==M+1) k--;
	build(1,1,k);
	for(int i=0;i<N;i++){
		conh().swap(con[k<<2]);
		for(node x:con[1]) con[k<<2].push_back(x);
		add(con[k<<2],1,0),downf(1,con[k<<2]);
		chg(1,1,k,mp[Y1[i]+1],mp[Y2[i]+2]-1);
	}
}
long long minimize(int A,int B){
	int l=1,r=con[1].size()-1,as=0;
	while(l<=r){
		int mid=(l+r)>>1;
		if(answ(con[1][mid-1],A,B)<=answ(con[1][mid],A,B)) r=mid-1;
		else l=mid+1,as=mid;
	}
	return answ(con[1][as],A,B);
}
//test begin
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	int n,m,q;
	vector<int>ga,gb;
	cin>>n>>m>>q;
	for(int i=1,x,y;i<=n;i++)
		cin>>x>>y,ga.push_back(x),gb.push_back(y);
	init(n,m,ga,gb);
	while(q--){
		int a,b;
		cin>>a>>b;
		cout<<minimize(a,b)<<"\n";
	}
	return 0;
}
//test end

L. [ABC369G] As far as possible

典中典了,直接把所有加权长链扔到大根堆里一个一个加就行了,时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,sn[N];
long long len[N],ans;
priority_queue<long long>q;
struct edge{
	int to,cs;
};
vector<edge>g[N];
inline void dfs(int x,int fa){
	for(edge y:g[x]) if(y.to!=fa){
		dfs(y.to,x);
		if(len[x]<len[y.to]+y.cs){
			q.push(len[x]),sn[x]=y.to;
			len[x]=len[y.to]+y.cs;
		}
		else q.push(len[y.to]+y.cs);
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		g[u].push_back({v,w});
		g[v].push_back({u,w});
	}
	dfs(1,0);
	q.push(len[1]);
	for(int i=1;i<=n;i++){
		if(q.size()) ans+=q.top(),q.pop();
		cout<<ans*2<<"\n";
	}
	return 0;
} 
posted @ 2025-12-28 15:43  white_tiger  阅读(2)  评论(0)    收藏  举报