做题笔记 - Feb. 2026

题目整理

[ABC236] Ex - Distinct Multiples

过年饺子醋醋饺子醋哦哦。

对于一个集合,让该集合内所有数相同的方案数是 \(\Big\lfloor\dfrac M{\operatorname{lcm} S}\Big\rfloor\),后面不再提到这部分贡献。

考虑将所有相同的数划分到一个连通块内,对连通块个数从大到小容斥。

按照连通块个数一层一层往上摞,则对于一种状态,要计算的是下面所有状态中可以通过合并连通块抵达这个状态的容斥系数和。从方案数的角度考虑,不同的连通块之间相对独立,权值应为各连通块容斥系数之积。

对于一个单独的连通块集合 \(S\),根据数学直觉,容斥系数 \(\mu\) 理应只和 \(|S|\) 有关。

\(\mu(n)\) 表示 \(|S|=n\) 时的容斥系数,则根据定义有 \(\left\{\begin{matrix}\mu(n)=1 & n=1\\\displaystyle \sum_{\bigcup T_i=[n],T_i\cap T_j=\varnothing}\prod_i\mu(|T_i|)=0 & n>1\end{matrix}\right.\)。考虑枚举某一个特定的值的连通块大小,记 \(\displaystyle M(n)=\sum_{x_i>0,\sum x_i=n}\prod_i \mu(x_i)=[n=1]\),则 \(\displaystyle \mu (n)=-\sum_{i=1}^{n-1}\mu(i)M(n-i)\binom{n-1}{i-1}=-(n-1)\mu(n-1)\),因此可以得出 \(\mu(n)=(-1)^{n-1}(n-1)!\)

容斥系数以外的贡献可以通过哈基幂 exp 做到 \(O(n^22^n)\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=1e1+9;
const int mod=998244353;
const int S=(1<<16)+9;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ull*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ull*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

int fac[N],ifac[N];
inline void Init(int lim){
	fac[0]=1;
	for(int i=1;i<=lim;i++) fac[i]=Mul(fac[i-1],i);
	ifac[lim]=Inv(fac[lim]);
	for(int i=lim-1;~i;i--) ifac[i]=Mul(ifac[i+1],i+1);
}
inline int C(int n,int m){
	if(m<0||m>n) return 0;
	else return Mul(fac[n],Mul(ifac[m],ifac[n-m]));
}

int f[S],w[S];
ll a[N],v[S],n,m;

inline ll LCM(ll x,ll y){
	x/=__gcd(x,y);
	if(y>m/x) return m+1;
	else return x*y;
}

signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];

	Init(n);
	w[0]=m%mod,v[0]=1;
	for(int sta=1;sta<(1<<n);sta++){
		int p=__lg(sta);
		v[sta]=LCM(v[sta^(1<<p)],a[p+1]);
		w[sta]=(m/v[sta])%mod;
		int c=__builtin_popcount(sta);
		MulAs(w[sta],fac[c-1]);
		if(~c&1) w[sta]=Sub(0,w[sta]);
	}
	f[0]=1;
	for(int sta=1;sta<(1<<n);sta++){
		int p=__lg(sta);
		for(int tta=sta;tta;tta=(tta-1)&sta){
			if(~tta>>p&1) continue ;
			AddAs(f[sta],Mul(f[sta^tta],w[tta]));
		}
	}

	cout<<f[(1<<n)-1]<<endl;

	return 0;
}

[CF995] F. Cowmpany Cowmpensation

考虑计算 \(f_{i,j}\) 表示考虑 \(i\) 的子树,\(i\) 填了 \(j(1\leq j\leq n)\) 的方案数,这是容易计算的。那么考虑对于根计算实际用了恰好 \(j\) 个的方案数,这是可以简单容斥的,算完了再乘个组合数就是答案,时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=3e3+9;
const int mod=1e9+7;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

int fac[N],ifac[N];
inline void Init(int lim){
	fac[0]=1;
	for(int i=1;i<=lim;i++) fac[i]=Mul(fac[i-1],i);
	ifac[lim]=Inv(fac[lim]);
	for(int i=lim-1;~i;i--) ifac[i]=Mul(ifac[i+1],i+1);
}
inline int C(int n,int m){
	if(m<0||m>n) return 0;
	else return Mul(fac[n],Mul(ifac[m],ifac[n-m]));
}
inline int B(int n,int m){
	if(m<0||m>n) return 0;
	int ans=ifac[m];
	for(int i=1;i<=m;i++) MulAs(ans,n-i+1);
	return ans;
}

vector<int> e[N];
int f[N][N],g[N],fa[N],n,m;
inline void DFS(int x){
	for(int i=1;i<=n;i++) f[x][i]=1;
	for(int y:e[x]){
		DFS(y);
		for(int i=1;i<=n;i++){
			AddAs(f[y][i],f[y][i-1]);
			MulAs(f[x][i],f[y][i]);
		}
	}
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n>>m;
	for(int i=2;i<=n;i++){
		cin>>fa[i];
		e[fa[i]].push_back(i);
	}

	Init(n);
	DFS(1);
	int ans=0;
	for(int i=1;i<=n;i++){
		g[i]=f[1][i];
		for(int j=1;j<i;j++) SubAs(g[i],Mul(g[j],C(i-1,j-1)));
		AddAs(ans,Mul(g[i],B(m,i)));
	}

	cout<<ans<<endl;

	return 0;
}

[HNOI2011] 卡农

为啥我没做过这个来着。

首先先把答案乘上 \(m!\) 变成集合序列。由于每个数出现次数是偶数,根据这个限制最后一个集合固定是前面集合的对称差。

考虑递推,设答案为 \(f_m\),那么最后一个为空集的方案数就是 \(f_{m-1}\),和之前的集合重了的方案数就是 \(f_{m-2}(m-1)(2^n-m+1)\)。因此有递推式 \(f_m=(m-1)!\dbinom{2^n-1}{m-1}-f_{m-1}-f_{m-2}(m-1)(2^n-m+1)\),可以 \(O(m)\) 计算。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=1e6+9;
const int mod=1e8+7;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ull*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ull*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

int fac[N],ifac[N];
inline void Init(int lim){
	fac[0]=1;
	for(int i=1;i<=lim;i++) fac[i]=Mul(fac[i-1],i);
	ifac[lim]=Inv(fac[lim]);
	for(int i=lim-1;~i;i--) ifac[i]=Mul(ifac[i+1],i+1);
}
inline int C(int n,int m){
	if(m<0||m>n) return 0;
	else return Mul(fac[n],Mul(ifac[n-m],ifac[m]));
}

int f[N],n,m;

signed main(){
	cin>>n>>m;

	Init(m);
	f[0]=1;
	for(int i=1,s=Sub(QPow(2,n),1),cur=1;i<=m;i++){
		f[i]=Sub(cur,f[i-1]);
		if(i>1) SubAs(f[i],Mul(i-1,Mul(f[i-2],Sub(s,i-2))));
		MulAs(cur,Sub(s,i-1));
	}

	cout<<Mul(f[m],ifac[m])<<endl;
	
	return 0;
}

[Ynoi2011] ODT

考虑轻重链剖分,类似 2log 做法,重儿子、本身和父亲特殊维护,单点查值的修改容易做到 1log。

考虑维护轻儿子的平衡树,把所有轻儿子按照子树大小降序排列,每 \(2^{2^i}\) 为一块进行分块,每块单独维护平衡树。那么修改时花 \(O(2^i)\) 的代价子树大小至少翻 \(2^{2^{i-1}}\) 倍,因此一次修改的总复杂度就是 \(O(\log n)\)。查询的时候使用多树二分一次就做到 \(\displaystyle \sum_{\sum2^{2^i}\leq s} O(\log 2^{2^i}) \simeq O(\log s)\),其中 \(s\) 是儿子个数。

总复杂度 \(O((n+q)\log n)\),但是多数二分带 \(6\) 倍常数,卡常卡不明白。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=1e6+9;
const ll inf=1e18;

struct Node{
	int son[2],siz;
	ll lk,rk;
}tr[N<<4];

int cnt;
inline int Allc(){return ++cnt;}
inline int Allc(ll key){
	int x=Allc();
	tr[x].son[0]=tr[x].son[1]=0,tr[x].siz=1,tr[x].lk=tr[x].rk=key;
	return x;
}
inline void PushUp(int x){
	if(tr[x].son[0]){
		tr[x].siz=tr[tr[x].son[0]].siz+tr[tr[x].son[1]].siz;
		tr[x].lk=min(tr[tr[x].son[0]].lk,tr[tr[x].son[1]].lk);
		tr[x].rk=max(tr[tr[x].son[0]].rk,tr[tr[x].son[1]].rk);
	}
}
inline int Bind(int lc,int rc){
	if(!lc||!rc) return lc|rc;
	int x=Allc();
	tr[x].son[0]=lc,tr[x].son[1]=rc;
	PushUp(x);
	return x;
}

double A=0.292;
double B=1/(2-A);
inline void Rotate(int x,bool f){
	int y=tr[x].son[f];
	tr[x].son[f]=tr[y].son[f];
	tr[y].son[f]=tr[y].son[f^1];
	tr[y].son[f^1]=tr[x].son[f^1];
	tr[x].son[f^1]=y;
	PushUp(y);
}
inline void Maintain(int x){
	bool f=(tr[tr[x].son[1]].siz>tr[tr[x].son[0]].siz);
	if(tr[tr[x].son[f]].siz>(1-A)*tr[x].siz){
		if(tr[tr[tr[x].son[f]].son[f^1]].siz>B*tr[x].siz){
			Rotate(tr[x].son[f],f^1);
		}
		Rotate(x,f);
	}
}

template<class T> inline int Make(T l,T r,int *a){
	if(r-l<=0) return 0;
	if(l+1==r) return Allc(a[*l]);
	T mid=l+((r-l)>>1);
	return Bind(Make(l,mid,a),Make(mid,r,a));
}
inline int Build(vector<int>::iterator l,vector<int>::iterator r,int *a){
	sort(l,r,[&](int i,int j){return a[i]<a[j];});
	return Make(l,r,a);
}
inline void Insert(int x,ll k){
	if(!tr[x].son[0]){
		tr[x].son[0]=Allc(min(tr[x].lk,k));
		tr[x].son[1]=Allc(max(tr[x].rk,k));
		return PushUp(x);
	}
	if(k<=tr[tr[x].son[0]].rk) Insert(tr[x].son[0],k);
	else Insert(tr[x].son[1],k);
	return PushUp(x),Maintain(x);
}
inline void Remove(int x,ll k,int f=-1){
	if(!tr[x].son[0]){
		if(tr[f].son[0]==x) tr[f]=tr[tr[f].son[1]];
		else tr[f]=tr[tr[f].son[0]];
		return ;
	}
	if(k<=tr[tr[x].son[0]].rk) Remove(tr[x].son[0],k,x);
	else Remove(tr[x].son[1],k,x);
	return PushUp(x),Maintain(x);
}

int tot;
vector<int> e[N];
int a[N],fa[N],siz[N],dep[N],hson[N],id[N],p[N],root[N][6],lim[N],n,q;
inline void GetHSon(int x){
	siz[x]=1;
	for(int y:e[x]){
		if(y==fa[x]) continue ;
		fa[y]=x;
		dep[y]=dep[x]+1;
		GetHSon(y);
		siz[x]+=siz[y];
	}
}
int dfn[N],top[N],dcnt;
inline void GetDFN(int x,int t){
	dfn[x]=++dcnt;
	top[x]=t;
	if(hson[x]) GetDFN(hson[x],t);
	for(int y:e[x]){
		if(y==fa[x]) continue ;
		if(y==hson[x]) continue ;
		GetDFN(y,y);
	}
}
inline int LCA(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		x=fa[top[x]];
	}
	return dep[x]<dep[y]?x:y;
}

ll tag[N],dlt[N];
struct Fenwick{
	ll tr[N];
	inline void Add(int x,ll k){while(x<=n) tr[x]+=k,x+=x&-x;}
	inline ll Ask(int x){ll sum=0;while(x) sum+=tr[x],x&=x-1;return sum;}
	inline ll Ask(int l,int r){return Ask(r)-Ask(l-1);}	
}T;
inline void Modify(int x,int y,int k){
	int z=LCA(x,y);
	T.Add(dfn[x],k),T.Add(dfn[y],k),T.Add(dfn[z],-k);
	if(fa[z]) T.Add(dfn[fa[z]],-k);
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		x=top[x];
		if(fa[x]){
			Remove(root[fa[x]][id[x]],a[x]+tag[x]);
			Insert(root[fa[x]][id[x]],a[x]+(tag[x]+=k));
		}
		x=fa[x];
	}
	if(dep[x]>dep[y]) swap(x,y);
	if(x==top[x]&&fa[x]){
		Remove(root[fa[x]][id[x]],a[x]+tag[x]);
		Insert(root[fa[x]][id[x]],a[x]+(tag[x]+=k));
	}
}
inline int Point(int x){
	tr[p[x]].lk=tr[p[x]].rk=a[x]+T.Ask(dfn[x],dfn[x]+siz[x]-1);
	return p[x];
}
inline void Join(int x,int k,vector<int> &v){
	if(!x) return ;
	if(tr[x].siz<=(1<<k)) return v.push_back(x);
	Join(tr[x].son[0],k,v);
	Join(tr[x].son[1],k,v);
}

mt19937 rng(4649);
template<class I,class F> inline I Select(I l,I r,int k,F Cmp){
	if(distance(l,r)<=1) return l;
	I i=l,j=r-1;
	I x=(l+rng()%(r-l));
	swap(*x,*l);
	x=l;
	int s=tr[*i].siz;
	while(i<j){
		while(i<j&&!Cmp(*j,*x)) j--;
		while(i<j&&!Cmp(*x,*i)) i++,s+=tr[*i].siz;
		if(i<j){
			s+=tr[*j].siz-tr[*i].siz;
			swap(*i,*j);
		}
	}
	if(k<=s) return Select(l,i+1,k,Cmp);
	else return Select(i+1,r,k-s,Cmp);
}
inline ll Query(int x,int k){
	vector<int> rt(root[x],root[x]+lim[x]);
	rt.push_back(Point(x));
	if(fa[x]) rt.push_back(Point(fa[x]));
	if(hson[x]) rt.push_back(Point(hson[x]));
	sort(rt.begin(),rt.end(),[](int u,int v){return tr[u].siz>tr[v].siz;});
	
	int m=0;
	for(int x:rt) m+=tr[x].siz;

	vector<int> lft;
	for(int t=__lg(tr[rt[0]].siz),i=0;~t;t--){
		vector<int> now;
		for(int x:lft) Join(x,t,now);
		while(i<rt.size()&&t<=__lg(tr[rt[i]].siz)) Join(rt[i++],t,now);

		int s=0;
		for(int x:now) s+=tr[x].siz;
		
		ll L=LLONG_MIN,R=LLONG_MAX;
		if(k<s){
			sort(now.begin(),now.end(),[](int u,int v){return tr[u].rk<tr[v].rk;});
			for(int j=0,cur=0;j<now.size();j++){
				R=tr[now[j]].rk;
				if((cur+=tr[now[j]].siz)>=k) break ;
			}
			// R=tr[*Select(now.begin(),now.end(),k,[](int u,int v){return tr[u].rk<tr[v].rk;})].rk;
		}
		if(m-k+1<s){
			sort(now.begin(),now.end(),[](int u,int v){return tr[u].lk>tr[v].lk;});
			for(int j=0,cur=0;j<now.size();j++){
				L=tr[now[j]].lk;
				if((cur+=tr[now[j]].siz)>=m-k+1) break ;
			}
			// L=tr[*Select(now.begin(),now.end(),m-k+1,[](int u,int v){return tr[u].lk>tr[v].lk;})].lk;
		}

		lft.clear();
		for(int x:now){
			if(tr[x].rk<L) m-=tr[x].siz,k-=tr[x].siz;
			else if(tr[x].lk>R) m-=tr[x].siz;
			else lft.push_back(x);
		}
	}

	return tr[lft[0]].lk;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);
	
	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}

	GetHSon(1);
	for(int x=1;x<=n;x++){
		p[x]=Allc(0);
		sort(e[x].begin(),e[x].end(),[](int u,int v){return siz[u]>siz[v];});
		
		int d=bool(fa[x]);
		if(e[x].size()>d) hson[x]=e[x][d];
		for(ll i=1+d,j=1;i<e[x].size();i+=(1ll<<j),j<<=1){
			for(ll k=i;k<i+(1ll<<j)&&k<e[x].size();k++) id[e[x][k]]=lim[x];
			if(i+(1ll<<j)>=e[x].size()) root[x][lim[x]]=Build(e[x].begin()+i,e[x].end(),a);
			else root[x][lim[x]]=Build(e[x].begin()+i,e[x].begin()+i+(1ll<<j),a);
			if(tr[root[x][lim[x]]].siz==1) Insert(root[x][lim[x]],inf);
			lim[x]++;
		}
	}
	GetDFN(1,1);

	while(q--){
		int op;
		cin>>op;
		if(op==1){
			int x,y,k;
			cin>>x>>y>>k;
			Modify(x,y,k);
		}else{
			int x,k;
			cin>>x>>k;
			cout<<Query(x,k)<<endl;
		}
	}

	return 0;
}

[JOISC 2016] Sushi

考虑分块,对于整块的操作整体上相当于尝试顶掉最大值,而细节上对于若干整块操作同时考虑所有操作不影响正确性。因此考虑对于每个块维护当前所有值的大根堆和整块操作的小根堆,散块直接暴力,时间复杂度 \(O(n\sqrt n\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=4e5+9;
const int S=7e2+9;

int a[N],L[N],R[N],blk[N],n,q,B;
priority_queue<int> val[S],opr[S];
inline void Build(){
	B=sqrt(n);
	for(int i=1;i<=n;i++) blk[i]=(i-1)/B+1;
	for(int i=1;i<=n;i++) R[blk[i]]=i;
	for(int i=n;i>=1;i--) L[blk[i]]=i;
	for(int x=1;x<=blk[n];x++){
		for(int i=L[x];i<=R[x];i++){
			val[x]=priority_queue<int>(a+L[x],a+R[x]+1);
		}
	}
}
inline void PushDown(int x){
	if(!opr[x].size()) return ;
	for(int i=L[x];i<=R[x];i++){
		opr[x].push(-a[i]);
		a[i]=-opr[x].top();
		opr[x].pop();
	}
	while(opr[x].size()) opr[x].pop();
}
inline int Brute(int l,int r,int x){
	PushDown(blk[l]);
	for(int i=l;i<=r;i++) if(x<a[i]) swap(x,a[i]);
	val[blk[l]]=priority_queue<int>(a+L[blk[l]],a+R[blk[l]]+1);
	return x;
}
inline int Swap(int l,int r,int x){
	if(blk[l]==blk[r]) return Brute(l,r,x);
	x=Brute(l,R[blk[l]],x);
	for(int i=blk[l]+1;i<blk[r];i++){
		opr[i].push(-x);
		val[i].push(x);
		x=val[i].top();
		val[i].pop();
	}
	return Brute(L[blk[r]],r,x);
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i];

	Build();
	while(q--){
		int l,r,x;
		cin>>l>>r>>x;
		if(l<=r) cout<<Swap(l,r,x)<<endl;
		else cout<<Swap(1,r,Swap(l,n,x))<<endl;
	}
	
	return 0;
}

[WC2022] 秃子酋长

你说得对但是 2log。

考虑分治,每次只处理跨越分治中心 \(m\) 的区间。

不失一般性地先考虑右边的贡献,提取出询问区间的所有值,考虑右边升序排序之后相邻的两个位置 \(i,j(a_i<a_j)\) 的贡献,若左边有数在 \((a_i,a_j)\) 中,那么贡献为 \((i-m)+(j-m)\),否则为 \(|i-j|\)。对于最小值最大值有类似的讨论。

对于右边的所有值,考虑找出 \((a_i,a_j)\) 在左边最右的值,对左端点这一维维护贡献,这是区间加。考虑从右到左扫描右端点,那么相当于删数,由于贡献只和相邻两个数有贡献,用链表即可维护删数的过程。

时间复杂度 \(O(n\log^2n)\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=5e5+9;

ll ans[N];
int a[N],p[N],ql[N],qr[N],n,q;
list<array<int,2>>::iterator pos[N];
vector<int> lq[N],rq[N];

struct Fenwick{
	ll tr[N];
	inline void Add(int x,ll k){while(x<=n) tr[x]+=k,x+=x&-x;}
	inline void Add(int l,int r,ll k){Add(l,k),Add(r+1,-k);}
	inline ll Ask(int x){ll sum=0;while(x) sum+=tr[x],x&=x-1;return sum;}
}T;

inline void Solve(int l,int r,vector<int> &v){
	if(l==r) return p[l]=l,void();
	
	int mid=l+r>>1;
	vector<int> L,R,Q;
	for(int i:v){
		if(qr[i]<=mid) L.push_back(i);
		else if(ql[i]>mid) R.push_back(i);
		else Q.push_back(i);
	}
	v.clear(),v.shrink_to_fit();
	Solve(l,mid,L),Solve(mid+1,r,R);
	inplace_merge(p+l,p+mid+1,p+r+1,[](int l,int r){return a[l]<a[r];});
	if(Q.empty()) return ;
	for(int i:Q){
		lq[ql[i]].push_back(i);
		rq[qr[i]].push_back(i);
	}

	int lmx=0;
	list<array<int,2>> li;
	for(int i=l;i<=r;i++){
		if(p[i]<=mid){
			if(!li.size()) lmx=max(lmx,p[i]);
			else li.back()[1]=max(li.back()[1],p[i]);
		}else pos[p[i]]=li.insert(li.end(),{p[i],0});
	}
	
	auto RMdf=[&](auto it,int k){
		if(li.empty()) return ;
		if(it==li.begin()){
			if(lmx) T.Add(l,lmx,k*(li.front()[0]-mid));
		}else if(it==li.end()){
			if(li.back()[1]) T.Add(l,li.back()[1],k*(li.back()[0]-mid));
		}else{
			auto jt=prev(it);
			if(!(*jt)[1]) T.Add(l,mid,k*abs((*jt)[0]-(*it)[0]));
			else{
				T.Add((*jt)[1]+1,mid,k*abs((*jt)[0]-(*it)[0]));
				T.Add(l,(*jt)[1],k*((*jt)[0]-mid+(*it)[0]-mid));
			}
		}
	};
	for(auto it=li.begin();it!=li.end();it++) RMdf(it,1);
	RMdf(li.end(),1);
	auto RErs=[&](auto it){
		auto jt=next(it);
		RMdf(it,-1),RMdf(jt,-1);
		if(it==li.begin()) lmx=max(lmx,(*it)[1]);
		else{
			auto kt=prev(it);
			(*kt)[1]=max((*kt)[1],(*it)[1]);
		}
		li.erase(it);
		RMdf(jt,1);
	};

	for(int i=r;i>mid;i--){
		for(int j:rq[i]) ans[j]+=T.Ask(ql[j]);
		RErs(pos[i]);
	}

	int lmn=r+1;
	for(int i=l;i<=r;i++){
		if(p[i]>mid){
			if(!li.size()) lmn=min(lmn,p[i]);
			else li.back()[1]=min(li.back()[1],p[i]);
		}else pos[p[i]]=li.insert(li.end(),{p[i],r+1});
	}

	auto LMdf=[&](auto it,int k){
		if(li.empty()) return ;
		if(it==li.begin()){
			if(lmn!=r+1) T.Add(lmn,r,k*(mid-li.front()[0]));
		}else if(it==li.end()){
			if(li.back()[1]!=r+1) T.Add(li.back()[1],r,k*(mid-li.back()[0]));
		}else{
			auto jt=prev(it);
			if((*jt)[1]==r+1) T.Add(mid+1,r,k*abs((*jt)[0]-(*it)[0]));
			else{
				T.Add(mid+1,(*jt)[1]-1,k*abs((*jt)[0]-(*it)[0]));
				T.Add((*jt)[1],r,k*(mid-(*jt)[0]+mid-(*it)[0]));
			}
		}
	};
	for(auto it=li.begin();it!=li.end();it++) LMdf(it,1);
	LMdf(li.end(),1);
	auto LErs=[&](auto it){
		auto jt=next(it);
		LMdf(it,-1),LMdf(jt,-1);
		if(it==li.begin()) lmn=min(lmn,(*it)[1]);
		else{
			auto kt=prev(it);
			(*kt)[1]=min((*kt)[1],(*it)[1]);
		}
		li.erase(it);
		LMdf(jt,1);
	};

	for(int i=l;i<=mid;i++){
		for(int j:lq[i]) ans[j]+=T.Ask(qr[j]);
		LErs(pos[i]);
	}

	for(int i=l;i<=r;i++) lq[i].clear(),rq[i].clear();
}

signed 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];
	for(int i=1;i<=q;i++) cin>>ql[i]>>qr[i];

	vector<int> Q(q);
	iota(Q.begin(),Q.end(),1);
	Solve(1,n,Q);

	for(int i=1;i<=q;i++) cout<<ans[i]<<endl;

	return 0;
}

[SWERC 2023] In-order

手玩一下不难发现,根据前序遍历和后续遍历可以得出二叉树的父子以及兄弟顺序关系,换言之,产生变化的只有恰含有一个儿子的节点,即无法确定是其左儿子还是右儿子。称这样的点为活动点。

先考虑敲定的区间的子树,该子树内唯独没有确定的是相对于这个区间的向外拐的子树的贡献,剩下的结构则已经全部确定。设区间内深度最小的节点为 \(p\),那么相当于每个点到 \(p\) 的路径上的活动点都固定了,这是可以统计的。

再考虑区间绝对位置带来的贡献。所有左子树对右子树的贡献都是固定的,所以可以调整的就是所有活动的祖先,先都钦定是左儿子,算出来设 \(l\) 处节点的 \(dfn\)\(x\),可活动的祖先有 \(y\) 个,那么会产生 \(\dbinom {y}{l-x}\) 的贡献。

剩下每个没统计过的活动点都会产生 \(2\) 的贡献。特别地,区间为空时答案为 \(2\) 的活动点个数次方, 区间长度恰为 \(1\) 且为活动点则儿子要分是左儿子还是右儿子讨论。

时间复杂度 \(O(n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=5e5+9;
const int mod=999999937;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ull*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ull*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

int fac[N],ifac[N];
inline void Init(int lim){
	fac[0]=1;
	for(int i=1;i<=lim;i++) fac[i]=Mul(fac[i-1],i);
	ifac[lim]=Inv(fac[lim]);
	for(int i=lim-1;~i;i--) ifac[i]=Mul(ifac[i+1],i+1);
}
inline int C(int n,int m){
	if(m<0||m>n) return 0;
	else return Mul(fac[n],Mul(ifac[m],ifac[n-m]));
}

int pr[N],in[N],ps[N],ips[N],iin[N],n;
int fa[N],dep[N],son[N][2],c[N],vis[N],dfn[N],dcnt;
inline void DFS(int x){
	if(son[x][0]) DFS(son[x][0]);
	dfn[x]=++dcnt;
	if(son[x][1]) DFS(son[x][1]);
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	cin>>n;
	for(int i=1;i<=n;i++) cin>>pr[i];
	for(int i=1;i<=n;i++) cin>>ps[i],ips[ps[i]]=i;
	for(int i=1;i<=n;i++){
		cin>>in[i];
		if(in[i]) iin[in[i]]=i;
	}

	for(int i=1;i<=n;i++){
		int t=pr[i-1];
		while(t&&ips[t]<ips[pr[i]]) t=fa[t];
		fa[pr[i]]=t;
		dep[pr[i]]=dep[t]+1;
		son[t][c[t]++]=pr[i];
	}

	if(accumulate(in+1,in+n+1,0ll)==0){
		int cnt=0;
		for(int i=1;i<=n;i++) cnt+=(c[i]==1);
		cout<<QPow(2,cnt)<<endl;
		return 0;
	}

	int l=1,r=n;
	while(l<=n&&!in[l]) l++;
	while(r>=1&&!in[r]) r--;

	for(int i=l;i<r;i++){
		int u=in[i],v=in[i+1];
		if(dep[u]<dep[v]) swap(u,v);
		int x=u;
		while(u){
			if(c[u]==1){
				if(iin[x]<iin[v]){
					if(!iin[u]||iin[x]>iin[u]){
						if(!vis[u]) swap(son[u][0],son[u][1]);
					}
				}else{
					if(iin[u]&&iin[u]<iin[x]){
						if(!vis[u]) swap(son[u][0],son[u][1]);
					}
				}
			}
			vis[u]=1;
			if(u==v) break ;
			u=fa[u];
		}
	}

	Init(n);
	DFS(pr[1]);
	int p=*min_element(in+l,in+r+1,[](int i,int j){return dep[i]<dep[j];}),cnt=0;
	vis[p]=1;
	while(p=fa[p]) cnt+=(c[p]==1),vis[p]=1;
	
	int res=C(cnt,l-dfn[in[l]]);
	cnt=0;for(int i=1;i<=n;i++) cnt+=(c[i]==1&&!vis[i]);

	int ans=Mul(res,QPow(2,cnt));

	if(l==r&&c[in[l]]==1){
		swap(son[in[l]][0],son[in[l]][1]);
		
		dcnt=0;
		DFS(pr[1]);
		int p=*min_element(in+l,in+r+1,[](int i,int j){return dep[i]<dep[j];}),cnt=0;
		vis[p]=1;
		while(p=fa[p]) cnt+=(c[p]==1),vis[p]=1;
		
		int res=C(cnt,l-dfn[in[l]]);
		cnt=0;for(int i=1;i<=n;i++) cnt+=(c[i]==1&&!vis[i]);

		AddAs(ans,Mul(res,QPow(2,cnt)));
	}

	cout<<ans<<endl;

	return 0;
}

[ROIR 2026] Скользящие окна

建出 \(l/k\) 坐标系,位置 \(i\) 作为最小值出现的是一个平行四边形。套路地将平行四边形先拆成 \(4\) 个 2-side 三角形,那么新坐标系是对 \(l+k\)\(l\) 做限制,而答案和 \(k\) 这维上的一个长条有关,因此把锐角 2-side 三角形拆成钝角 2-side 三角形减去 2-side 矩形,则变成对 \(l+k/k\) 以及 \(l/k\) 做限制,扫描线可以简单维护,时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=2e5+9;

ll ans[N];
int a[N],prv[N],suc[N],ql[N],qr[N],qk[N],n,q;

struct Fenw1ck{
	ll tr[N];
	inline void Add(int x,ll k){while(x<=n+n) tr[x]+=k,x+=x&-x;}
	inline ll Ask(int x){ll sum=0;while(x) sum+=tr[x],x&=x-1;return sum;}
	inline void Clear(){for(int i=1;i<=n+n;i++) tr[i]=0;}
};
struct Fenw2ck{
	Fenw1ck T1,T2;
	inline void Modify(int l,int r,ll k){
		T1.Add(l,k),T1.Add(r+1,-k);
		T2.Add(l,l*k),T2.Add(r+1,-k*(r+1));
	}
	inline ll Query(int x){return (x+1)*T1.Ask(x)-T2.Ask(x);}
	inline ll Query(int l,int r){return Query(r)-Query(l-1);}
	inline void Clear(){T1.Clear(),T2.Clear();}
}T1,T2;

vector<int> q_[N];
vector<array<int,2>> m1[N],m2[N];

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i];

	vector<int> stk;
	for(int i=1;i<=n;i++){
		while(stk.size()&&a[i]<=a[stk.back()]){
			suc[stk.back()]=i;
			stk.pop_back();
		}
		prv[i]=stk.size()?stk.back():0;
		stk.push_back(i);
	}
	for(int x:stk) suc[x]=n+1;

	auto PushMdf=[&](int k,int x,int w){
		m1[k].push_back({k+x,w});
		m2[k].push_back({x+1,-w});
	};
	for(int i=1;i<=n;i++){
		PushMdf(0,i,a[i]);
		if(prv[i]) PushMdf(i-prv[i],prv[i],-a[i]);
		PushMdf(suc[i]-i,i,-a[i]);
		if(prv[i]) PushMdf(suc[i]-prv[i],prv[i],a[i]);
	}
	
	for(int i=1;i<=q;i++) cin>>ql[i]>>qr[i]>>qk[i],q_[--qk[i]].push_back(i),qr[i]-=qk[i];

	for(int k=0;k<=n;k++){
		for(auto p:m1[k]) T1.Modify(p[0],n+n,p[1]);
		for(auto p:m2[k]) T2.Modify(p[0],n+n,p[1]);
		for(int i:q_[k]){
			ans[i]+=T1.Query(ql[i]+k,qr[i]+k);
			ans[i]+=T2.Query(ql[i],qr[i]);
		}
	}

	for(int i=1;i<=q;i++) cout<<ans[i]<<endl;

	return 0;
}

幻梦 | Dream with Dynamic

不难发现一次 P 操作会让区间内所有值全部映射到 \([0,64)\) 的某个值。考虑将答案拆成上一次 P 操作的值的 \(\operatorname{popcount}\)(成为真值)再加上 \(k\),并维护 \(k\) 的连续段。一次 P 操作对 \(k\) 相同的连续段会对段内的真值作用一个 \(\mathbb{Z}_{64}\rightarrow \mathbb{Z}_{64}\) 的映射,并将 \(k\) 清零,合并整个操作区间。用线段树维护所有映射的复合,时间复杂度 \(O(n\log n\log V)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=3e5+9;
const int B=64;

int a[N],n,q;

struct Fenwick{
	ll tr[N];
	inline void Add(int x,ll k){while(x<=n) tr[x]+=k,x+=x&-x;}
	inline void Add(int l,int r,ll k){Add(l,k),Add(r+1,-k);}
	inline ll Ask(int x){ll sum=0;while(x) sum+=tr[x],x&=x-1;return sum;}
}T;
struct SgT{
	struct P{
		unsigned char p[B];
		P(){}
		P(ll x){
			for(int i=0;i<B;i++) p[i]=__builtin_popcountll(i+x);
		}

		inline unsigned char& operator [](int x){return p[x];}
		friend inline void operator *=(P &p,P &q){
			for(int i=0;i<B;i++) p[i]=q[p[i]];
		}
		inline void Init(){iota(p,p+B,0);}
		inline void Clear(){memset(p,0,sizeof p);}
	};
	struct Node{
		int l,r;
		bool cov;
		P tag;
	}tr[N<<2];
	inline void Push(int x,P &k){tr[x].tag*=k,tr[x].cov=1;}
	inline void PushDown(int x){
		if(!tr[x].cov) return ;
		Push(x<<1,tr[x].tag);
		Push(x<<1|1,tr[x].tag);
		tr[x].cov=0,tr[x].tag.Init();
	}
	inline void Build(int x,int l,int r){
		tr[x].l=l,tr[x].r=r,tr[x].tag.Init();
		if(tr[x].l==tr[x].r) return tr[x].tag.Clear();
		int mid=tr[x].l+tr[x].r>>1;
		Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
	}
	inline void Modify(int x,int l,int r,P &k){
		if(l<=tr[x].l&&tr[x].r<=r) return Push(x,k);
		PushDown(x);
		int mid=tr[x].l+tr[x].r>>1;
		if(l<=mid) Modify(x<<1,l,r,k);
		if(r>mid) Modify(x<<1|1,l,r,k);
	}
	inline int Query(int x,int pos,int p){
		if(tr[x].l==tr[x].r) return tr[x].tag[p];
		int mid=tr[x].l+tr[x].r>>1;
		if(pos<=mid) return tr[x].tag[Query(x<<1,pos,p)];
		else return tr[x].tag[Query(x<<1|1,pos,p)];
	}
}S;

set<int> o;
inline void Add(int l,int r,int k){
	o.insert(l),o.insert(r+1);
	T.Add(l,r,k);
}
inline void Popc(int l,int r){
	auto lt=o.insert(l).first;
	auto rt=o.insert(r+1).first;
	for(auto it=lt;it!=rt;it++){
		int i=*it,j=*next(it)-1;
		ll x=T.Ask(i);
		T.Add(i,j,-x);
		SgT::P p(x);
		S.Modify(1,i,j,p);
	}
	o.erase(++lt,rt);
}
inline ll Query(int x){return S.Query(1,x,0)+T.Ask(x);}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i];

	S.Build(1,1,n);
	for(int i=1;i<=n;i++) T.Add(i,i,a[i]);
	for(int i=1;i<=n+1;i++) o.insert(o.end(),i);
	while(q--){
		char op;
		cin>>op;
		if(op=='A'){
			int l,r,k;
			cin>>l>>r>>k;
			Add(l,r,k);
		}else if(op=='P'){
			int l,r;
			cin>>l>>r;
			Popc(l,r);
		}else if(op=='J'){
			int x;
			cin>>x;
			cout<<Query(x)<<endl;
		}
	}

	return 0;
}

不连续子串 / subseq

嘟嘟嘟哒哒哒。

考虑对内层子序列 \(T\) 计数合法外层子序列 \(S\) 的贡献。设 \(f_i\) 表示内层子序列恰在 \(i\) 结束的贡献,方便起见我们认为内外层子序列如果有重复出现只算第一次。那么对于内层的转移,设从 \(j\) 转移到 \(i\),为了保证 \(T\) 唯一,有限制:\(S\)\(j\)\(i\) 之间不存在位置 \(p\) 使得 \(a_ p=a_i\)。同时为了保证 \(S\) 唯一,\(S\) 满足 \(s_{i-1}\geq pre_i\)。因此转移的时候对合法的 \(S\) 的个数进行 DP 即可,时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=8e3+9;
const int mod=1e9+7;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

int a[N],pre[N],buc[N],f[N],g[N],d[N],s[N],n;

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) pre[i]=exchange(buc[a[i]],i);

	f[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=i,cur=0;j>=0;j--){
			g[j]=i!=j?cur:1;
			if(a[i]!=a[j]||i==j){
				AddAs(d[j],g[j]);
				SubAs(d[pre[j]],g[j]);
			}
			AddAs(f[i],Mul(f[j],g[j]));
			AddAs(cur,exchange(d[j],0));
		}
		s[i]=Add(f[i],Sub(s[i-1],s[max(pre[i]-1,0)]));
		AddAs(s[i],s[i-1]);
	}

	cout<<s[n]<<endl;

	return 0;
}
posted @ 2026-02-17 12:52  JoeyJiang  阅读(0)  评论(0)    收藏  举报