2026 NOI 做题记录(十一)



Contest Link

\(\text{By DaiRuiChen007}\)



A. [QOJ10215] Gona Guni (4)

Problem Link

首先最小点覆盖可以从下到上贪心,枚举一个连通块,能生成该连通块的 \(S\) 数量就是二的非叶子节点数次方,可以在 dp 过程中简单维护。

对于 \(m\) 次方的限制用第二类斯特林数变成求 \(\sum \binom v0\sim\sum\binom vm\),组合意义变成在覆盖集中选 \(m\) 个特殊点,直接树形背包。

时间复杂度 \(\mathcal O(nm)\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5,MOD=998244353;
void add(int &x,const int &y) { x=x+y>=MOD?x+y-MOD:x+y; }
void sub(int &x,const int &y) { x=x>=y?x-y:x+MOD-y; }
int n,m,f[MAXN][2][205],g[2][205],w[205],sz[MAXN];
vector<int> G[MAXN];
void mul(int *x,int *y,int *z,int l,int r) {
	for(int i=0;i<=l;++i) for(int j=0;j<=r&&i+j<=m;++j) {
		z[i+j]=(z[i+j]+1ll*x[i]*y[j])%MOD;
	}
}
void dfs(int u,int fz) {
	memset(f[u],0,sizeof(f[u]));
	f[u][0][0]=f[u][1][0]=1,sz[u]=1;
	for(int v:G[u]) if(v^fz) {
		dfs(v,u);
		memcpy(g,f[u],sizeof(g));
		for(int i=0;i<=sz[v];++i) add(f[v][0][i],f[v][1][i]);
		mul(f[u][0],f[v][1],g[0],sz[u],sz[v]);
		mul(f[u][1],f[v][0],g[1],sz[u],sz[v]);
		for(int i=0;i<=sz[v];++i) sub(f[v][0][i],f[v][1][i]);
		memcpy(f[u],g,sizeof(g));
		sz[u]=min(sz[u]+sz[v],m);
	}
	for(int i=0;i<=sz[u];++i) sub(f[u][1][i],f[u][0][i]);
	for(int i=sz[u];i;--i) add(f[u][1][i],f[u][1][i-1]);
	sub(f[u][0][0],1);
	for(int i=0;i<=sz[u];++i) {
		add(f[u][0][i],f[u][0][i]),add(w[i],f[u][0][i]);
		add(f[u][1][i],f[u][1][i]),add(w[i],f[u][1][i]);
	}
	for(int v:G[u]) if(v^fz) {
		for(int i=0;i<=sz[v];++i) {
			sub(w[i],f[v][1][i]),sub(w[i],f[v][0][i]);
			if(i<m) sub(w[i+1],f[v][0][i]);
		}
	}
	add(f[u][0][0],1);
}
int S[205][205],fac[205];
void solve() {
	cin>>n>>m,memset(w,0,sizeof(w));
	for(int i=1;i<=n;++i) G[i].clear();
	for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
	dfs(1,0),add(w[0],n);
	int ans=0;
	for(int i=0;i<=m;++i) ans=(ans+1ll*w[i]*S[m][i]%MOD*fac[i])%MOD;
	cout<<ans<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	const int M=200;
	for(int i=fac[0]=1;i<=M;++i) fac[i]=1ll*fac[i-1]*i%MOD;
	for(int i=0;i<=M;++i) {
		S[i][0]=!i;
		for(int j=1;j<=i;++j) S[i][j]=(S[i-1][j-1]+1ll*j*S[i-1][j])%MOD;
	}
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



B. [QOJ9048] Lazy Susan (4.5)

Problem Link

每个限制就是在某个时刻前循环移位要经过某个区间,记录循环位移经过的区间 \([l,r]\)\(f_{l,r,0/1}\) 表示当前循环移位为 \(l/r\),到达该状态的最早时刻。

考虑区间 \([r+1,l-1]\) 内部最早的限制就能判定 \(f_{l,r}\) 是否合法了。

此时要对每个 \(f_{x,x}\) 分别做一遍 dp,直接转置原理倒过来计算到达 \((l,r,0/1)\) 最晚的合法时刻即可。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2.5e7+5;
int n,m,a[5005],f[MAXN][2],g[MAXN];
int o(int l,int r) { return (l%n+n)%n*n+(r%n+n)%n; }
void solve() {
	cin>>n>>m,memset(f,0x3f,(n*n)<<3),memset(g,0x3f,(n*n)<<2);
	for(int i=0;i<n;++i) cin>>a[i];
	for(int i=1,p,d,t,x;i<=m;++i) {
		cin>>p>>d>>t;
		if(2*a[p]+1<n) x=o(p-a[p]-d,p+a[p]-d),g[x]=min(g[x],t);
	}
	for(int d=2;d<=n;++d) for(int l=0,r=d-1;l<n;++l,r=(r+1)%n) g[o(l,r)]=min({g[o(l,r)],g[o(l+1,r)],g[o(l,r-1)]});
	for(int d=n-1;d;--d) for(int l=0,r=d-1;l<n;++l,r=(r+1)%n) {
		int x=f[o(l-1,r)][0],y=f[o(l,r+1)][1],z=g[o(r+1,l-1)];
		f[o(l,r)][0]=max(min(x,z)-1,min(y,z)-d),f[o(l,r)][1]=max(min(y,z)-1,min(x,z)-d);
	}
	for(int i=0;i<n;++i) cout<<(f[o(i,i)][0]>=0);
	cout<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



*C. [QOJ1114] Not a work of Idol (9.5)

Problem Link

\(x_0,\sim x_{k-1}\) 表示 \(x\)\(p\) 进制分解。

首先考虑把某些图分成 \(p\) 倍数的组,使得这些图的贡献被消除。

考虑选出 \(0\sim p-1\) 这些点,把这些点的标号循环移位,如果得到了不同的图,很显然每种循环移位都能得到不同的图。

所以我们要求这 \(p\) 个点到剩余点要么同时有边要么同时没边,且这 \(p\) 个点内,如果 \((i,(i+k)\bmod p)\) 有边,则所有 \((j,(j+k)\bmod p)\) 之间有边。

由于这 \(p\) 个点到外界连边相同,所以可以把这些点看成一个大小为 \(p\) 的组。

那么对于 \(p\) 个这样的组,我们可以再次考虑它们的循环移位,得到组内边类似的限制,并且得到大小为 \(p^2\) 的组。

以此类推,则我们能得到 \(n_i\) 个大小为 \(p^i\) 的组。

首先考虑组内的边,对于一个大小为 \(p^k\) 的组,显然内部每个点度数 \(b\) 都相同且 \(\in[0,p^{k-1})\),由于任意两个组内部连边完全一致,所以 \(b_ip^i\) 只能由 \(p^{i+1}\) 组内 \(p^i\) 的点之间连边得到。

所以方案数对 \(b\) 的每一位独立,我们设 \(w_x\) 表示 \(p\) 个点的组,每个点度数为 \(x\) 的方案,则方案数为 \(\prod w_{b_i}\)

然后考虑组间连边的贡献,此时处理的难点在于一条边同时影响两侧的度数,我们需要用简单的方式处理其中一侧的度数贡献。

\(i<j\),考虑 \(p_i\to p_j\) 连边的贡献,首先 \(\le p^i\) 的组对其的贡献不超过 \(p^{i+1}\),而 \(p_i\to p_j\) 的贡献必定是 \(p_j\) 倍数,所以 \(p_i\to p_j\) 连边的贡献必定是 \(d_j\times p^j\)

因此我们只要对每个 \(p^r\) 考虑所有 \(l<r\)\(p^l\) 对其的贡献,假设我们已经处理了 \(p^0\sim p^{l-1}\) 的组对 \(p^r\) 的贡献,则当前度数之和 \(<2p^l\),所以此时每个组的度数为 \(\sum_{i<l}d_ip^i\)\(p^{l}+\sum_{i<l}d_ip^i\)

那么我们只要记录这些 \(p^r\) 组中有几个取到 \(p^{l}+\sum_{i<l} d_ip^i\) 即可,转移的时候枚举每个 \(p^r\) 内部的 \(p^l\) 组连边形态,从而算出每个 \(p^r\) 被几个 \(p^l\) 连接,而 \(p^l\) 点的限制就是每个点恰好连接 \(d_r\)\(p^r\),可以用一些暴力的方法预处理出系数。

然后特殊处理一下 \(p^r\) 组之间的边,对于 \(>r\) 的边会在更大的 \(p^r\) 上处理。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXS=1<<17|5;
int _,p,z[7],f[7][7][7][7][7][7],h[7][7][7];
//f[n,x,m,y,u,v]: n=N_r x=D_l m=N_l y=D_r u=#[in deg>=p^l], v=#[out deg>=p^l+1]
//h[n,x,u]: edge between p^r groups to solve D_r
int g[MAXS],ng[MAXS];
void add(int &x,const int &y) { x=(x+y)%p; }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>_>>p;
	for(int i=z[0]=1;i<p;++i) z[i]=z[i-1]*p;
	for(int n=0;n<p;++n) for(int y=0;y<p;++y) {
		basic_string <int> tr; //state of p^l
		for(int s=0;s<(1<<n);++s) if(__builtin_popcount(s)==y) {
			int d=0;
			for(int i=0;i<n;++i) d+=z[i]*(s>>i&1);
			tr+=d;
		}
		memset(g,0,z[n]<<2),g[0]=1; //p^r deg from m p^l
		for(int m=0;m<p;++m) {
			for(int x=0;x<p;++x) for(int u=0;u<=n;++u) {
				for(int e=0;e<(1<<(n*2));++e) { //edge of p^l inside p^r
					int ok=1,v=0,s=0,o=1; //s: need deg from p^l
					for(int i=0;i<n;++i) {
						int c=e>>(i*2)&3;
						if(c>p/2) { ok=0; break; }
						int d=c*(p>2?2:1)+(i<u); //choose edge: C(p/2,c)
						if(p==5&&c==1) o*=2;
						if(p==7&&(c==1||c==2)) o*=3;
						if(d<=x) s+=z[i]*(x-d);
						else s+=z[i]*(x-d+p),++v; //carry bit
					}
					if(!ok) continue;
					add(f[n][x][m][y][u][v],o*g[s]);
				}
			}
			if(m==p-1||y>n) break;
			memset(ng,0,z[n]<<2);
			for(int s=0;s<z[n];++s) if(g[s]) for(auto&e:tr) add(ng[s+e],g[s]);
			memcpy(g,ng,z[n]<<2);
		}
	}
	for(int n=0;n<p;++n) {
		memset(g,0,z[n]<<2);
		basic_string <int> e;
		for(int i=0;i<n;++i) for(int j=i+1;j<n;++j) e+=(z[i]+z[j]);
		int k=e.size();
		for(int s=0;s<(1<<k);++s) { //graph of n p^r
			int o=0;
			for(int i=0;i<k;++i) o+=(s>>i&1)*e[i];
			add(g[o],1);
		}
		for(int x=0;x<p;++x) for(int u=0;u<=n;++u) {
			if(!x&&u) continue;
			int s=0;
			for(int i=0;i<n;++i) s+=z[i]*(x-(i<u));
			add(h[n][x][u],g[s]);
		}
	}
	for(ll N,D;_--;) {
		cin>>N>>D;
		basic_string <int> n,d;
		for(;N;N/=p,D/=p) n+=N%p,d+=D%p;
		int ans=1;
		for(int r=0;r<(int)n.size();++r) {
			array<int,7> dp,nx;
			dp.fill(0),dp[0]=1;
			for(int l=0;l<r;++l) {
				nx.fill(0);
				for(int u=0;u<=n[r];++u) if(dp[u]) for(int v=0;v<=n[r];++v) {
					add(nx[v],dp[u]*f[n[r]][d[l]][n[l]][d[r]][u][v]);
				}
				dp=nx;
			}
			int rs=0;
			for(int u=0;u<=n[r];++u) add(rs,dp[u]*h[n[r]][d[r]][u]);
			ans=ans*rs%p;
		}
		cout<<ans<<"\n";
	}
	return 0;
}



D. [QOJ9047] Knights of Night (4)

Problem Link

直接模拟费用流,增广路只会在开头和结尾经过一个不在当前匹配中的点,因此暴力维护 \(k\) 个匹配点之间的边权。

对于非匹配点,我们只关心每个点到所有非匹配点的边权最大值。

预处理出每个点权值最大的 \(k\) 条出边就能在增广过程中动态维护每个点边权最大的非匹配邻居,然后 SPFA 求最短路并增广即可。

时间复杂度 \(\mathcal O(nk+k^4)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5,P=998244353;
const ll inf=1e18;
array<int,2> e[MAXN];
int n,m,k,a[2][MAXN],rk[2][MAXN],b[2][MAXN][205];
vector <int> G[2][MAXN];
bool vis[MAXN],in[2][MAXN];
int p[405],to[405],fr[205],pb[MAXN],q[MAXN*20],at[MAXN];
ll h[405],d[205],w[205];
bool inq[205],ban[205][205];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m>>k;
	for(int o:{0,1}) {
		for(int i=1;i<=n;++i) cin>>e[i][0],e[i][1]=i;
		sort(e+1,e+n+1);
		for(int i=1;i<=n;++i) rk[o][e[i][1]]=i,a[o][i]=e[i][0];
	}
	for(int i=1,u,v;i<=m;++i) {
		cin>>u>>v,G[0][rk[0][u]].push_back(rk[1][v]),G[1][rk[1][v]].push_back(rk[0][u]);
	}
	for(int o:{0,1}) for(int u=1;u<=n;++u) {
		int c=0,t=lower_bound(a[o^1]+1,a[o^1]+n+1,P-a[o][u])-a[o^1]-1;
		for(int x:G[o][u]) vis[x]=1;
		for(int i=t,j=n;c<k;) {
			for(;i>0&&vis[i];--i);
			for(;j>t&&vis[j];--j);
			if(i<=0&&j<=t) break;
			if(j<=t||(i>0&&a[o^1][i]>a[o^1][j]-P)) b[o][u][c++]=i--;
			else b[o][u][c++]=j--;
		}
		for(int x:G[o][u]) vis[x]=0;
	}
	ll ans=0;
	for(int t=0;t<k;++t) {
		array <ll,3> z={-inf,0,0};
		for(int i=1;i<=n;++i) if(!in[0][i]) {
			int *B=b[0][i],&j=pb[i];
			for(;B[j]&&in[1][B[j]];++j);
			if(B[j]) z=max(z,array<ll,3>{(a[0][i]+a[1][B[j]])%P,i,B[j]});
		}
		int l=1,r=0;
		for(int i=0;i<2*t;++i) {
			h[i]=-inf,to[i]=0;
			for(int x:b[i&1][p[i]]) if(x&&!in[(i&1)^1][x]) {
				to[i]=x,h[i]=(a[i&1][p[i]]+a[(i&1)^1][x])%P; break;
			} else if(!x) break;
		}
		memset(at,-1,sizeof(at));
		for(int i=0;i<t;++i) {
			fr[i]=-1,at[p[i<<1|1]]=i;
			if(to[i<<1|1]) inq[i]=1,d[i]=h[i<<1|1]-w[i],q[++r]=i;
			else inq[i]=0,d[i]=-inf;
		}
		memset(ban,0,sizeof(ban));
		for(int i=0;i<t;++i) for(int x:G[0][p[i<<1]]) if(~at[x]) ban[i][at[x]]=1;
		while(l<=r) {
			int u=q[l++]; inq[u]=0;
			for(int v=0;v<t;++v) if(v!=u&&!ban[u][v]) {
				ll x=d[u]+(a[0][p[u<<1]]+a[1][p[v<<1|1]])%P-w[v];
				if(d[v]<x) {
					fr[v]=u,d[v]=x;
					if(!inq[v]) inq[v]=1,q[++r]=v;
				}
			}
		}
		for(int i=0;i<t;++i) if(to[i<<1]) z=max(z,array<ll,3>{d[i]+h[i<<1],i,0});
		if(z[0]<=-inf/10) {
			for(;t<k;++t) cout<<-1<<" \n"[t==k-1];
			break;
		}
		ans+=z[0],cout<<ans<<" \n"[t==k-1];
		if(z[2]) {
			p[t<<1]=z[1],p[t<<1|1]=z[2],w[t]=z[0],in[0][z[1]]=in[1][z[2]]=1;
			continue;
		}
		int y=t; in[1][p[t<<1|1]=to[z[1]<<1]]=1;
		for(int x=z[1];~x;y=x,x=fr[x]) swap(p[y<<1],p[x<<1]);
		in[0][p[y<<1]=to[y<<1|1]]=1;
		for(int i=0;i<=t;++i) w[i]=(a[0][p[i<<1]]+a[1][p[i<<1|1]])%P;
	}
	return 0;
}



E. [QOJ10118] Expression (3)

Problem Link

首先如果我们能生成变量 \(w=0\),则 \([x\le w]=\overline x\),从而能表示 \(x<y=\overline{y\le x},\max(x,y)=\overline {\min(\overline x,\overline y)}\),所以只要能生成 \(0\) 即可。

如果任意一个变量为 \(0\),那么取所有变量的 \(\min\) 就能得到 \(0\)

否则全部变量为 \(1\),归纳证明此时用 $\min,\le $ 只能生成 \(1\),所以合法当且仅当此时原式取值为 \(1\)

时间复杂度 \(\mathcal O(|S|^2)\)

代码:

#include<bits/stdc++.h>
using namespace std;
char s[100005];
string o;
pair<int,string> solve(int l,int r) {
	if(l==r) return {1,string(1,s[l])};
	if(s[l+1]=='m') {
		int p=l+5;
		for(int z=0;;++p) {
			z+=(s[p]=='('?1:(s[p]==')'?-1:0));
			if(!z) break;
		}
		auto x=solve(l+5,p),y=solve(p+2,r-1);
		if(s[l+2]=='i') return {min(x.first,y.first),"(min "+x.second+" "+y.second+")"};
		return {max(x.first,y.first),"((min ("+x.second+" <= "+o+") ("+y.second+" <= "+o+")) <= "+o+")"};
	}
	int p=l+1;
	for(int z=0;;++p) {
		z+=(s[p]=='('?1:(s[p]==')'?-1:0));
		if(!z) break;
	}
	auto x=solve(l+1,p),y=solve(p+4+(s[p+3]=='='),r-1);
	if(s[p+3]=='=') return {x.first<=y.first,"("+x.second+" <= "+y.second+")"};
	return {x.first<y.first,"(("+y.second+" <= "+x.second+") <= "+o+")"};
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int n=0;
	for(char c=getchar();c!='\n';c=getchar()) s[++n]=c;
	for(int i=1,v=0;i<=n;++i) if('a'<=s[i]&&s[i]<='j'&&s[i-1]!='m'&&!(v>>(s[i]-'a')&1)) {
		v|=1<<(s[i]-'a');
		if(o.empty()) o+=s[i];
		else o="(min "+o+" "+s[i]+")";
	}
	auto z=solve(1,n);
	if(!z.first) return cout<<"NO\n",0;
	cout<<"YES\n"<<z.second<<"\n";
	return 0;
}



F. [QOJ10985] Perfect Suika Game on a Tree (5)

Problem Link

首先朴素做法就是按值从小到大,每种值都必须存在完美匹配。

考虑对每个点 \(u\) 判断 \(u\) 的所有子树是否合法。

首先如果子树中有 \(<a_u\) 的点必定非法,且子树中 \(<a_u\) 的点必须合成 \(\ge a_u\) 的点,且至多有一个子树合成出 \(a_u\)

并且 \(>a_u\) 的点每种子树合成出来之后不能有相同的,否则对应时刻无法跨过 \(u\) 匹配。

那么我们用线段树合并维护子树内的 \(\sum a_u\),修改操作只会影响 \(\mathcal O(1)\) 棵线段树,所以可以维护,需要实现进退位,可以通过预处理全 \(1\) 和全 \(0\) 的子树完成。

时间复杂度 \(\mathcal O((n+q)\log V)\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5,MAXM=4e7+5,V=1<<18;
int n,m,tot=0,O[2][20],ls[MAXM],rs[MAXM],sz[MAXM];
void psu(int p) { sz[p]=sz[ls[p]]+sz[rs[p]]; }
int upd(int &v,int l,int r,int p) {
	if(!v) return p;
	if(v>0&&sz[p]==r-l+1) return O[0][__lg(r-l+1)];
	if(v<0&&!sz[p]) return O[1][__lg(r-l+1)];
	if(l==r) return v=0,O[sz[p]^1][0];
	int u=++tot,mid=(l+r)>>1;
	ls[u]=upd(v,l,mid,ls[p]),rs[u]=upd(v,mid+1,r,rs[p]);
	return psu(u),u;
}
int merge(int &v,int l,int r,int q,int p) {
	if(!p||!q) return upd(v,l,r,p|q);
	if(l==r) {
		v+=sz[p]+sz[q]; int c=v&1; v>>=1;
		return O[c][0];
	}
	int u=++tot,mid=(l+r)>>1;
	ls[u]=merge(v,l,mid,ls[q],ls[p]);
	rs[u]=merge(v,mid+1,r,rs[q],rs[p]);
	return psu(u),u;
}
int add(int x,int &v,int l,int r,int p) {
	if(l==r) {
		v+=sz[p]; int c=v&1; v>>=1;
		return O[c][0];
	}
	int u=++tot,mid=(l+r)>>1;
	if(x<=mid) {
		ls[u]=add(x,v,l,mid,ls[p]);
		rs[u]=upd(v,mid+1,r,rs[p]);
	} else ls[u]=ls[p],rs[u]=add(x,v,mid+1,r,rs[p]);
	return psu(u),u;
}
int qry(int x,int l,int r,int p) {
	if(!x) return 0;
	if(r<=x) return sz[p];
	int mid=(l+r)>>1,s=qry(x,l,mid,ls[p]);
	if(mid<x) s+=qry(x,mid+1,r,rs[p]);
	return s;
}
int o,rt[MAXN*2],fa[MAXN],id[MAXN],a[MAXN],vs[MAXN],ban=0;
struct Edge { int v,i; };
vector <Edge> G[MAXN];
void dfs(int u) {
	for(auto e:G[u]) if(e.v^fa[u]) {
		id[e.i]=e.v,fa[e.v]=u,dfs(e.v),vs[u]+=sz[rt[e.v]];
		rt[u+n]=merge(o=0,1,V,rt[u+n],rt[e.v]);
	}
	rt[u]=add(a[u],o=1,1,V,rt[u+n]);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	for(int c:{0,1}) {
		O[c][0]=++tot;
		for(int i=1;i<18;++i) O[c][i]=++tot,ls[tot]=rs[tot]=O[c][i-1];
	}
	for(int i=0;i<18;++i) sz[O[1][i]]=1<<i;
	cin>>n;
	for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back({v,i}),G[v].push_back({u,i});
	for(int i=1;i<=n;++i) cin>>a[i];
	dfs(1);
	cin>>m;
	if(sz[rt[1]]>1) {
		for(int i=1;i<=m;++i) cout<<"No\n";
		return 0;
	}
	for(int u=1;u<=n;++u) ban+=(sz[rt[u+n]]<vs[u])+(qry(a[u]-1,1,V,rt[u])>0);
	for(int u,v,e;m--;) {
		cin>>e,v=id[e],u=fa[v];
		ban-=(sz[rt[u+n]]<vs[u])+(qry(a[v]-1,1,V,rt[v])>0)+(qry(a[u]-1,1,V,rt[u])>0),vs[u]-=sz[rt[v]];
		swap(a[u],a[v]),rt[v]=add(a[v],o=1,1,V,rt[v+n]),rt[u+n]=add(a[u],o=-1,1,V,rt[u]);
		vs[u]+=sz[rt[v]],ban+=(sz[rt[u+n]]<vs[u])+(qry(a[v]-1,1,V,rt[v])>0)+(qry(a[u]-1,1,V,rt[u])>0);
		cout<<(ban?"No\n":"Yes\n");
	}
	return 0;
}



G. [QOJ10323] 2-Power Rush (4.5)

Problem Link

首先要刻画 \(\sum 2^ic_i=n\) 状物,把 \(c_i\) 写成二进制形式,\(n\) 的第 \(i\) 位由 \(c_x\) 的第 \(i-x\) 位转移,所以预处理每位贡献后从低往高 dp 进位即可。

此时可以 \(\mathcal O(\log^3V)\) 计算某个数的答案,但是难以通过。

我们注意到 dp 时,每位上的转移独立且只和 \(n\) 在这一位的值有关,所以可以考虑 Meet-In-Middle。

具体来说我们分别枚举并计算低 \(15\) 位和高 \(15\) 位的每种取值下的最终 dp 结果,高 \(15\) 位不难用转置原理倒序从而在中间合并。

时间复杂度 \(\mathcal O(\sqrt V\log^3V+T\log V)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=998244353;
ll f[1<<15][45],g[1<<15][45],h[45][45],a[45],b[45];
ll qry(ll n) {
	ll z=0;
	for(int i=0;i<30;++i) z=(z+f[n&32767][i]*g[n>>15][i])%MOD;
	return z;
}
ll w[45];
signed main() {
	for(int i=0;i<30;++i) {
		h[i][0]=1;
		for(int j=0;j<=i;++j) {
			if(j==i) w[j]=1<<j;
			else w[j]=w[j]*w[j]%MOD;
			for(int k=j;~k;--k) h[i][k+1]=(h[i][k+1]+h[i][k]*w[j])%MOD;
		}
	}
	for(int s=0;s<(1<<15);++s) {
		memset(a,0,sizeof(a)),a[0]=1;
		for(int i=0;i<15;++i) {
			memset(b,0,sizeof(b));
			for(int x=0;x<30;++x) for(int y=0;y<30;++y) if((x+y)%2==(s>>i&1)) {
				b[(x+y)/2]=(b[(x+y)/2]+a[x]*h[i][y])%MOD;
			}
			memcpy(a,b,sizeof(a));
		}
		for(int x=0;x<30;++x) f[s][x]=a[x];
		memset(a,0,sizeof(a)),a[0]=1;
		for(int i=14;~i;--i) {
			memset(b,0,sizeof(b));
			for(int x=0;x<30;++x) for(int y=0;y<30;++y) if((x+y)%2==(s>>i&1)) {
				b[x]=(b[x]+a[(x+y)/2]*h[i+15][y])%MOD;
			}
			memcpy(a,b,sizeof(a));
		}
		for(int x=0;x<30;++x) g[s][x]=a[x];
	}
	ll t,A,B,p=1<<30,z=0;
	cin>>t>>A>>B;
	for(int i=0;i<t;++i) z+=qry(B)^i,B=(B+A)%p;
	cout<<z<<"\n";
	return 0;
}



H. [QOJ10324] Swap Counter (4)

Problem Link

对每个值 \(x\) 考虑交换的过程首先 \(x\) 前面每个 \(>x\) 的元素会让 \(x\) 位置减一,不存在这样的元素时 \(x\) 就会直接运动到位置 \(x\) 上。

那么考虑从后往前用栈维护此过程,栈里维护 \(\ge i\) 且位置 \(<i\) 的元素,如果栈中元素 \(>b_{i-1}\) 那么把栈里的最大值填在 \(i\) 上,然后把次大值填在 \(i+1\) 上,以此类推。

注意这里我们还要用栈从小到大维护可以填的位置,很显然填一个元素时,前面的位置都填了比该元素大的值,所以该元素一定会移动到 \(i\) 上。

不难证明此时从 \(n\sim 1\) 每个元素都落在最靠后的可能位置,且无法构造当且仅当 \(b_{i-1}>b_i+1\),容易证明这是充分大。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5;
int n,a[MAXN],b[MAXN],p[MAXN],q[MAXN];
void solve() {
	cin>>n;
	for(int i=1;i<n;++i) cin>>b[i];
	int l=1,r=0,t=0;
	for(int i=n-1;i;--i) {
		q[++r]=i+1,p[++t]=i+1;
		while(r-l+1>b[i]&&t) a[p[t--]]=q[l++];
		if(b[i]!=r-l+1) return cout<<"-1\n",void();
	}
	for(p[++t]=1;l<=r;) a[p[t--]]=q[l++];
	a[p[t]]=1;
	for(int i=1;i<=n;++i) cout<<a[i]<<" \n"[i==n];
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



*I. [QOJ10269] Easy Counting Problem (8)

Problem Link

首先算法就是从前往后贪心删元素,设当前删了 \(k\) 个,则可以删除 \(i\) 当且仅当 \(a_i\in[i-k,i]\)

如果 \(n\le m\),则此时 \(a_i\) 恰好有 \(k+1\) 种取值使得 \(k\) 增加。

那么 \(f_{i,k}\) 表示对应方案数,则 \(f_{i,k}=f_{i-1,k}(m-k-1)+kf_{i-1,k-1}\),我们要求 \(\sum kf_{i,k}\)

很显然维护所有 \(f_{i,k}\) 不可能,不妨直接关心 \(g_i=\sum kf_{i,k}\),则 \(g_{i+1}=\sum ((k+1)^2+k(m-k-1))f_{i,k}=\sum ((m+1)k+1)f_{i,k}=(m+1)g_{i}+m^i\)

因此 \(g_{i+1}=(m+1)g_i+m^i\),归纳得到 \(g_i=(m+1)^i-m^i\)

如果 \(m\le n\),可以发现 \(k\) 较大的时候 \(a_i\) 范围是 \(a_i\in[i-k,m]\),这要求 \(k\ge i-m\ge n-m\)

所以我们的思路依旧是维护 \(g_i=\sum kf_{i,k}\),然后按 \(f_{i,k}=f_{i-1,k}(i-k-1)+f_{i-1,k-1}(m-i+k)\) 转移 \(g_i\),但是用暴力维护所有 \(k\le n-m\) 的值并正确计算它们对 \(g_{i+1}\) 的贡献。

问题变成快速求初值 \(f_{m,k}\),表示成 \(\sum_{x_1<x_2<\cdots<x_k}k!\prod_i(m-i)^{x_i-x_{i-1}+1}\),可以发现组合意义就是长度为 \(n\) 值域 \([1,m-1]\) 的包含 \(1\sim k\) 序列个数,\(k!\) 枚举每种数首次出现的顺序然后乘剩下位置的方案数。

那么新的问题直接容斥即可,因此 \(f_{m,k}=\sum_{i\le k}\binom ki(-1)^{k-i}(m-1-i)^m\)

时间复杂度 \(\mathcal O((n-m)^2)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2005,MOD=998244353;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll n,m,f[MAXN],C[MAXN][MAXN],pw[MAXN];
ll q(ll i,ll j) { return max(0ll,m-i+j+1); }
void solve() {
	cin>>n>>m,memset(f,0,sizeof(f));
	if(m>=n) return cout<<(ksm(m,n)*(n+1)+MOD-ksm(m+1,n))%MOD<<"\n",void();
	int k=min(n-m,m);
	ll z=(ksm(m+1,m)+MOD-ksm(m,m))%MOD,p=ksm(m,m);
	for(int i=0;i<=k;++i) {
		pw[i]=ksm(m-1-i,m);
		for(int j=0;j<=i;++j) f[i]=(f[i]+(j&1?MOD-C[i][j]:C[i][j])*pw[j])%MOD;
	}
	for(int i=m+1;i<=n;++i) {
		z=((m+1)*z+(m-i+1+MOD)*p)%MOD,p=p*m%MOD;
		for(int j=n-m;~j;--j) {
			ll x=(f[j]*(m-q(i,j))+(j?f[j-1]*q(i,j-1):0))%MOD;
			ll y=(f[j]*(i-j-1)+(j?f[j-1]*(m-i+j):0))%MOD;
			z=(z+(MOD-y+x)*j)%MOD,f[j]=x;
		}
	}
	cout<<(ksm(m,n)*n+MOD-z)%MOD<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	for(int i=0;i<MAXN;++i) for(int j=C[i][0]=1;j<=i;++j) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



J. [QOJ10322] Matching Query (5)

Problem Link

首先对于值为 \(v\)\(v+1\) 的元素匹配,一定选 \(v\) 的前缀和 \(v+1\) 的后缀,所以某种排列合法当且仅当任意 \(v\)\((v-1,v)\)\((v,v+1)\) 的匹配个数之和不超过 \(v\) 的出现次数 \(c_v\)

\(x_v\) 表示 \((v,v+1)\) 的匹配个数,可以用主席树动态维护 \(x_v\) 的上界,设为 \(b_v\)

考虑用线性规划刻画之:\(\begin{cases}x_i\le b_i\\x_{i-1}+x_i\le c_i\\\max \sum x_i\end{cases}\),对偶得到 \(\begin{cases}y_i+z_i+z_{i+1}\\\min\sum y_ib_i+z_ic_i\end{cases}\),即选择每条边或点要付出对应 \(b_i\)\(c_i\) 的代价,要求每条边或其两端点至少有一个被选。

可以证明只要考虑 \(y_i,z_i\in\{0,1\}\) 的解,然后把答案和 \(\dfrac n2\) 取较小值即可。

那么可以线段树维护 ddp 处理。

时间复杂度 \(\mathcal O((n+q)\log n)\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5,MAXM=4e7+5,inf=1e9;
struct Segt1 {
	int su[MAXM],mn[MAXM],ls[MAXM],rs[MAXM],tot;
	void psu(int p) {
		su[p]=su[ls[p]]+su[rs[p]];
		mn[p]=min(mn[ls[p]],su[ls[p]]+mn[rs[p]]);
	}
	void add(int x,int z,int l,int r,int &p) {
		if(!p) p=++tot;
		if(l==r) return su[p]+=z,mn[p]=min(0,su[p]),void();
		int mid=(l+r)>>1;
		if(x<=mid) add(x,z,l,mid,ls[p]);
		else add(x,z,mid+1,r,rs[p]);
		psu(p);
	}
}	S;
int n,m,q,a[MAXN],b[MAXN],c[MAXN],rt[MAXN];
struct Segt2 {
	int f[1<<20|5][2][2];
	void upd(int i,int l=0,int r=m-1,int p=1) {
		if(l==r) {
			f[p][0][0]=0,f[p][1][0]=f[p][1][1]=f[p][0][1]=c[i];
			return ;
		}
		int mid=(l+r)>>1;
		if(i<=mid) upd(i,l,mid,p<<1);
		else upd(i,mid+1,r,p<<1|1);
		for(int u:{0,1}) for(int v:{0,1}) {
			f[p][u][v]=inf;
			for(int x:{0,1}) for(int y:{0,1}) {
				f[p][u][v]=min(f[p][u][v],f[p<<1][u][x]+f[p<<1|1][y][v]+(x||y?0:b[mid]));
			}
		}
	}
}	T;
void upd(int x) {
	b[x]=(c[x]+c[(x+1)%m]+2*S.mn[rt[x]]-S.su[rt[x]])/2;
}
void add(int x,int k) {
	c[a[x]]+=k;
	S.add(x,k,1,n,rt[a[x]]),S.add(x,-k,1,n,rt[(a[x]+m-1)%m]);
	upd(a[x]),upd((a[x]+m-1)%m);
	T.upd(a[x]),T.upd((a[x]+m-1)%m);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m>>q;
	for(int i=1;i<=n;++i) cin>>a[i],add(i,1);
	for(int x,y;q--;) {
		cin>>x>>y,add(x,-1),a[x]=y,add(x,1);
		int z=n/2;
		for(int l:{0,1}) for(int r:{0,1}) z=min(z,T.f[1][l][r]+(l||r?0:b[m-1]));
		cout<<z<<"\n";
	}
	return 0;
}



*K. [QOJ10984] One Different Inequality (7)

Problem Link

首先考虑最多有多少个符号满足,可以发现只要不同时选择相邻两个相反的符号即可。

假设我们已经知道了哪些符号被满足,则满足的符号构成的连续段内部符号同向,所以可以直接删掉这些符号,考虑连续段之间的相对关系就能确定原排列。

所以我们只要计算有多少个排列满足未被选的符号对应的大小关系。

这就是经典问题了,把所有大于号容斥成小于号或无限制,然后求所有小于号极长连续段的阶乘乘积,可以 dp 记录当前段的结尾。

回到原问题,把原序列分成若干相邻符号两两不同的连续段,每个连续段分长度奇偶性讨论,如果是奇数,则未选符号确定,否则是一个前缀为一种符号,一个后缀为另一种符号。

考虑 CDQ 分治优化 dp,首先在整段之间 CDQ 分治,此时只要处理每个位置是当前连续段中的首个或最后一个段端点的情况,很显然可以 NTT 优化,然后再对每段内部 CDQ 分治优化即可。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353,N=1<<19;
int rev[N],inv[N],fac[N],ifac[N],w[N<<1];
namespace P {
int ksm(int a,int b=MOD-2) { int s=1; for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) s=1ll*s*a%MOD; return s; }
void poly_init() {
	inv[1]=1;
	for(int i=2;i<N;++i) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
	fac[0]=ifac[0]=1;
	for(int i=1;i<N;++i) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*inv[i]%MOD;
	for(int k=1;k<=N;k<<=1) {
		int x=ksm(3,(MOD-1)/k); w[k]=1;
		for(int i=1;i<k;++i) w[i+k]=1ll*x*w[i+k-1]%MOD;
	}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y;  }
void ntt(int *f,bool idft,int n) {
	for(int i=0;i<n;++i) {
		rev[i]=(rev[i>>1]>>1);
		if(i&1) rev[i]|=n>>1;
	}
	for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
	for(int k=2,x,y;k<=n;k<<=1) {
		for(int i=0;i<n;i+=k) {
			for(int j=i;j<i+k/2;++j) {
				x=f[j],y=1ll*f[j+k/2]*w[k+j-i]%MOD;
				f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
			}
		}
	}
	if(idft) {
		reverse(f+1,f+n);
		for(int i=0,x=ksm(n);i<n;++i) f[i]=1ll*f[i]*x%MOD;
	}
}
int poly_mul(const int *f,const int *g,int *h,int n,int m) {
	static int a[N],b[N];
	for(int i=0;i<n;++i) a[i]=f[i];
	for(int i=0;i<m;++i) b[i]=g[i];
	int len=plen(n+m-1);
	ntt(a,0,len),ntt(b,0,len);
	for(int i=0;i<len;++i) h[i]=1ll*a[i]*b[i]%MOD;
	ntt(h,1,len);
	memset(a,0,sizeof(int)*len);
	memset(b,0,sizeof(int)*len);
	return len;
}
}
int n,m,a[N],b[N],f[N],g[N],h[N];
//b=0:0011, b=1:1100, b=2:000, b=3:111
char s[N];
void cdq2(int l,int r) {
	if(l==r) return ;
	int mid=(l+r)>>1;
	cdq2(l,mid);
	for(int i=mid,z=1;i>=l;--i) g[i-l]=1ll*f[i]*z%MOD,z=MOD-z;
	int len=P::poly_mul(g,ifac,h,mid-l+1,r-l+1);
	for(int i=mid+1,z=1;i<=r;++i) f[i]=(f[i]+1ll*h[i-l]*z)%MOD,z=MOD-z;
	memset(g,0,len<<2),memset(h,0,len<<2);
	cdq2(mid+1,r);
}
void cdq1(int l,int r) {
	if(l==r) return b[l]^2?cdq2(a[l-1]+1,a[r]):void();
	int mid=(l+r)>>1,p=a[l-1]+1;
	cdq1(l,mid);
	for(int i=mid,z=1;i>=l;--i) if(b[i]^2) {
		int x=1;
		for(int j=a[i];j>a[i-1];--j) g[j-p]=1ll*f[j]*z%MOD*x%MOD,x=(b[i]==1)+MOD-x;
		if(b[i]==0) x=((a[i]-a[i-1])&1)^1;
		z=1ll*z*x%MOD;
	}
	int len=P::poly_mul(g,ifac,h,a[mid]-p+1,a[r]-p+1);
	for(int i=mid+1,z=1;i<=r;++i) if(b[i]^2) {
		int x=1;
		for(int j=a[i-1]+1;j<=a[i];++j) f[j]=(f[j]+1ll*h[j-p]*z%MOD*x)%MOD,x=(!b[i])+MOD-x;
		if(b[i]==1) x=((a[i]-a[i-1])&1)^1;
		z=1ll*z*x%MOD;
	}
	memset(g,0,len<<2),memset(h,0,len<<2);
	cdq1(mid+1,r);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	P::poly_init();
	cin>>n,--n,a[++m]=1,b[m]=3;
	for(int i=1;i<=n;++i) cin>>s[i];
	for(int i=1,j;i<=n;i=j) {
		for(j=i+1;j<=n&&s[j-1]!=s[j];++j);
		if(j-i>1) a[++m]=(j-i)/2,b[m]=(j-i)%2*2+(s[i]=='<');
	}
	a[++m]=1,b[m]=3;
	for(int i=1;i<=m;++i) a[i]+=a[i-1];
	f[1]=1,cdq1(1,m);
	cout<<1ll*f[a[m]]*fac[a[m]-1]%MOD<<"\n";
	return 0;
}



*L. [QOJ11117] 尾声之下 (7.5)

Problem Link

首先一个点集合法可以用如下方式检验:动态维护每个强连通分量,显然都是区间,每次合并两个能互相到达的相邻的区间直到所有点被合并。

朴素的想法就是 \(f_{l,r,A,B}\) 表示区间 \([l,r]\) 中,可达范围为 \([A,B]\) 的强连通分量数量。

但直接 dp 无法确定转移顺序,把判定过程唯一化,从左到右加入每个点,如果当前点不能放进强连通分量中就继续向后合并直到能和当前强连通分量合并。

那么 \(f_{l,r,A,B}\) 的转移就是在 \([r+1,n]\) 中选一个点集 \(S\) 满足只有 \(a_{\max(S)}\le r\),用 \(g_{l,r,A,B}\) 表示 \(S\subseteq [l,n],\max (S)=r,\max_{x\in S,x\ne r}a_x>A,\max b_x=B\) 的强连通分量数即可。

稍作优化可以做到 \(\mathcal O(n^2)\) 转移。

时间复杂度 \(\mathcal O(n^6)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,a[55],b[55];
ll f[55][55][55][55],g[55][55][55][55],ans;
//g[l,r,x,y], choose (l,r), a[l,r)>x, max b=y
void mul(ll *w,ll *u,ll *v,int p) {
	ll s=0,t=0;
	for(int i=p;i<=n;++i) s+=u[i],w[i]+=v[i]*s+u[i]*t,t+=v[i];
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i) {
		cin>>a[i]>>b[i],f[i][i][a[i]][b[i]]=1;
		for(int j=1;j<=i;++j) g[i][i][j][b[i]]=1;
	}
	for(int l=n;l;--l) for(int r=l;r<=n;++r) for(int x=1;x<=l;++x) for(int L=r+1;L<=n;++L) for(int R=L;R<=n;++R) if(a[R]<=r) {
		mul(f[l][R][min(x,a[R])],f[l][r][x],g[L][R][r],L);
		if(a[r]>x) mul(g[l][R][x],g[l][r][x],g[L][R][r],L);
	}
	for(int l=n;l;--l) for(int r=l;r<=n;++r) for(int x=1;x<=l;++x) for(int y=r;y<=n;++y) ans+=f[l][r][x][y];
	cout<<ans%998244353<<"\n";
	return 0;
}



*M. [QOJ10114] Anthem (7)

Problem Link

分析得到可能的操作只有如下几种:

  • 把相邻相同的两个字符变成一个。
  • 删除开头或结尾的奇回文串的一半。
  • 找到 \(a+S+b+\mathrm{rev}(S)+a+S+b\) 的子串变成 \(a+S+b\)

可以证明操作一进行时刻必是前缀且操作二进行时刻必是后缀。

操作二可以每次都后缀 Manacher 直到找到回文后缀然后删掉,复杂度和删掉字符同阶,均摊 \(\mathcal O(n)\)

那么问题就是如何快速进行操作三。

可以把上面的子串看成两个互相包含中间字符的奇回文串。

考虑增量维护,我们只要快速维护每个前缀的所有回文后缀,以及每个位置作为中心的最长回文串,就能加入字符了。

注意到如果当前字符串有两个回文后缀长为 \(x,y\)\(x\in [y,2y)\),则这两个后缀互相包含回文中心,必定能导出删除。

因此任何时候我们维护的字符串每个位置的回文后缀只有 \(\mathcal O(\log n)\) 个。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int N,n=0,f[MAXN]; //palin radius
char s[MAXN];
basic_string <int> p[MAXN]; //palin suffix
void ins(char c) {
	s[++n]=c,p[n].clear();
	for(int x:p[n-1]) if(2*x-n>0&&s[2*x-n]==c) p[n]+=x,f[x]=n;
	for(int x:p[n]) if(f[2*x-n]>=x) {
		n=2*x-n;
		for(int y:p[n]) f[y]=n;
		return ;
	}
	p[n]+=n,f[n]=n;
}
void upd() {
	do {
		s[0]='{',s[n+1]='}',f[n]=0,N=n;
		for(int i=n-1,l=n,r=n;i>=1;--i) {
			f[i]=i>=l?min(i-l+1,f[l+r-i]):0;
			for(;s[i-f[i]]==s[i+f[i]];++f[i]);
			if(i+f[i]>n) { n=i; break; }
			if(i-f[i]+1<l) l=i-f[i]+1,r=i+f[i]-1;
		}
	} while(N>n);
}
signed main() {
	string str;
	cin>>N>>str;
	for(auto c:str) if(c!=s[n]) ins(c);
	upd(),reverse(s+1,s+n+1),upd(),reverse(s+1,s+n+1);
	cout<<n<<"\n"<<string(s+1,s+n+1)<<"\n";
	return 0;
}



N. [QOJ10739] Odd and Even (4)

Problem Link

求前缀和后变成选子序列使相邻不同的字符对最多和最少,最多只要贪心在每个同色段中选一个即可。

否则看成最大化有 \(k\) 对不同字符时的子序列长度,这是经典贪心。

从选出所有段开始,每次让相异字符数 \(-2\) 肯定是在原本基础上调整调整,即找一个极长区间使得每个段都是选与不选交替,然后把整个段的选择情况翻转。

容易用链表配合堆快速维护每个区间。

注意到长度 \(>1\) 的同色段只有 \(\mathcal O(n)\) 个,用堆维护后快速处理长度 \(=1\) 的段即可。

时间复杂度 \(\mathcal O((n+m)\log n)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=4e5+5;
int n,m,q,l[MAXN],r[MAXN];
ll a[MAXN],f[MAXN];
bool vis[MAXN];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>q;
	ll sg=0,sm=0,c1=0;
	for(ll i=1,v,k,d=1;i<=n+1;++i) {
		if(i>n) { a[++m]=d; break; }
		cin>>v>>k,v&=1,sm+=k,sg+=k*v;
		if(!v) d+=k;
		else {
			a[++m]=d,--k,d=1,c1+=k/2;
			if(k&1) a[++m]=1,--k;
		}
	}
	priority_queue <array<ll,2>,vector<array<ll,2>>,greater<>> Q;
	int k=(m-1)/2;
	for(int i=2;i<m;++i) l[i]=i-1,r[i]=i+1,Q.push({a[i],i});
	auto del=[&](int x) { if(1<x&&x<m) vis[x]=1,l[r[x]]=l[x],r[l[x]]=r[x]; };
	for(int t=0;t<k;++t) {
		while(vis[Q.top()[1]]) Q.pop();
		int x=Q.top()[1]; Q.pop(),f[t+1]=f[t]+a[x];
		if(l[x]==1||r[x]==m) { del(x),del(l[x]),del(r[x]); continue; }
		Q.push({a[x]=a[l[x]]+a[r[x]]-a[x],x}),del(l[x]),del(r[x]);
	}
	for(ll z,o;q--;) {
		cin>>z,o=min(sg,z-((sg^z)&1));
		if(z>=sm-c1)  { cout<<o<<" "<<z-(sg-2*(sm-z))<<"\n"; continue; }
		int i=upper_bound(f,f+k+1,sm-c1-z)-f-1;
		cout<<o<<" "<<z-(sg-2*(c1+i))<<"\n";
	}
	return 0;
}



O. [QOJ10725] RDDCCD (2.5)

Problem Link

首先答案一定是 \(\mathrm{lcm}(a_{1,1},\mathrm{rev}(a_{1,1}))\) 的因数,因此只要对 \(\mathcal O(\log V)\) 个形如 \(p^t\) 的数判断是否所有 \(p^t\mid a_{i,j}\) 即可。

直接预处理,如果 \(a_{i,j}\)\(\mathrm{rev}(a_{i,j})\) 同时是或不是 \(p^t\) 的倍数,则可以直接跳过这些 \((i,j)\),否则相当于限制若干对角线 \(x_{i+j},y_{i-j}\) 的反转状态必须相同或不同。

可以直接对每个连通块处理,但简单的做法是直接维护所有被翻转的 \((i,j)\) 的哈希值异或和并判断,每次操作就是异或上一条对角线上的哈希值之和,容易维护。

时间复杂度 \(\mathcal O((n^2+q)\log V)\)

代码:

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=1005;
mt19937_64 rnd(time(0));
int rv(int x) { int y=0; for(;x;x/=10) y=y*10+x%10; return y; }
int n,m=0,q,a[MAXN][MAXN],b[MAXN][MAXN],w[64];
ull h[MAXN][MAXN],X[64][MAXN*2],Y[64][MAXN*2],Z[64];
map <int,int> mp;
void work(int x) {
	for(int i=2,c;i*i<=x;++i) if(x%i==0) {
		for(c=0;x%i==0;x/=i,++c);
		mp[i]=max(mp[i],c);
	}
	if(x>1) mp[x]=max(mp[x],1);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>q;
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) cin>>a[i][j],b[i][j]=rv(a[i][j]),h[i][j]=rnd();
	work(a[1][1]),work(b[1][1]);
	for(auto it:mp) {
		for(int p=it.first,k=it.second,z=p;k--;z*=p) {
			for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(a[i][j]%z&&b[i][j]%z) goto nx;
			w[++m]=p;
			for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) {
				if(a[i][j]%z||b[i][j]%z) X[m][i+j]^=h[i][j],Y[m][i-j+n]^=h[i][j];
				if(a[i][j]%z) Z[m]^=h[i][j];
			}
		}
		nx:;
	}
	for(char o;q--;) {
		int x,g=1; cin>>o>>x;
		for(int i=1;i<=m;++i) Z[i]^=(o=='+'?X[i][x]:Y[i][x+n]),g*=Z[i]?1:w[i];
		cout<<g<<"\n";
	}
	return 0;
}



P. [QOJ10737] Black Red Tree (3)

Problem Link

动态维护每个点 \(u\) 子树内距 \(u\)\(0\sim k\) 的点个数 \(f_{u,k}\),修改的时候只要更新经过该边的路径数,可以枚举 \(\mathrm{LCA}\) 并处理,对 \(f\) 的更新同理。

时间复杂度 \(\mathcal O(nk^2)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
struct Edge { int v,w; };
vector <Edge> G[MAXN];
int n,m,fa[MAXN],dsu[MAXN],f[MAXN][15],id[MAXN],b[15];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
ll ans=0;
void dfs(int u,int fz) { fa[u]=fz; for(auto e:G[u]) if(e.v^fz) id[e.w]=e.v,dfs(e.v,u); }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m,iota(dsu,dsu+n+1,0);
	for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back({v,i}),G[v].push_back({u,i});
	dfs(1,0);
	for(int u=1;u<=n;++u) {
		for(int k=0,x=u,y=0;k<=m&&x;++k,y=x,x=fa[x]) {
			++f[x][k],ans+=f[x][m-k];
			if(y&&m>k) ans-=f[y][m-k-1];
		}
	}
	for(int t=1,u,k;t<n;++t) {
		memset(b,0,sizeof(b));
		cin>>u,b[k=0]=u=id[(u+ans)%(n-1)+1];
		for(;k<=m&&b[k]!=1;++k) b[k+1]=find(fa[b[k]]);
		for(int i=0;i<k;++i) {
			int x=b[i],y=b[i+1];
			for(int j=0;i+j<=m;++j) {
				ans+=1ll*f[u][j]*f[y][m-i-j];
				if(i+j<m) ans-=1ll*f[u][j]*f[y][m-i-j-1];
				if(i+j<m) ans-=1ll*f[u][j]*f[x][m-i-j-1];
				if(i+j<m-1) ans+=1ll*f[u][j]*f[x][m-i-j-2];
			}
		}
		for(int i=1;i<=k;++i) {
			for(int j=0;j+i-1<=m;++j) {
				if(j+i<=m) f[b[i]][j+i]-=f[u][j];
				f[b[i]][j+i-1]+=f[u][j];
			}
		}
		cout<<ans<<"\n";
		dsu[u]=fa[u];
	}
	return 0;
}



Q. [QOJ10272] Majority Graph (3)

Problem Link

枚举众数 \(x\),对于 \(a_i=x\) 作为左右端点的情况,只要扫描线然后用堆维护所有左端点,弹出一段前缀连边后放回最小值即可。

如果左端点 \(l\)\(a_l\ne x\),找到后面首个 \(a_p=x\),则 \([l,r]\) 合法,任意 \(x\in[l,p]\)\([x,p]\) 都合法,只要把 \([l,p)\)\(p\) 连边即可。

因此我们只要枚举所有存在合法区间的左端点 \(l\),注意到对于 \(l\) 的每个后缀,如果绝对众数切换,则长度至少翻倍,因此这样的 \(l\) 均摊只有 \(\mathcal O(n\log n)\) 个,可以直接枚举。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e6+5;
int n,dsu[MAXN],a[MAXN],s[MAXN],mx[MAXN];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
void link(int x,int y) { dsu[find(x)]=find(y); }
void sol(basic_string<int>&A) {
	int m=0;
	for(int x:A) a[++m]=x;
	a[m+1]=n+1;
	for(int i=1;i<=m;++i) s[i]=s[i-1]+1-(a[i]-a[i-1]-1);
	for(int i=m;i>=1;--i) mx[i]=i<m?max(mx[i+1],s[i]):s[i];
	priority_queue <array<int,2>,vector<array<int,2>>,greater<>> Q;
	for(int i=1;i<=m;++i) {
		Q.push({s[i]-1,a[i]});
		for(int j=a[i],v=s[i]-1;j>a[i-1]&&v<mx[i];--j,++v) link(j,a[i]);
		if(Q.size()&&Q.top()[0]<s[i]) {
			auto o=Q.top(); Q.pop();
			for(int j=a[i],v=s[i];j<a[i+1]&&v>o[0];++j,--v) link(o[1],j);
			for(;Q.size()&&Q.top()[0]<s[i];Q.pop()) link(Q.top()[1],a[i]);
			Q.push(o);
		}
	}
}
basic_string<int>p[MAXN];
void solve() {
	cin>>n;
	for(int i=1;i<=n;++i) p[i].clear(),dsu[i]=i;
	for(int i=1,x;i<=n;++i) cin>>x,p[x]+=i;
	for(int i=1;i<=n;++i) if(p[i].size()) sol(p[i]);
	int c=0;
	for(int i=1;i<=n;++i) c+=dsu[i]==i;
	cout<<c<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



R. [QOJ10330] Median Operations (4)

Problem Link

把排列转成 \(-1,0,1\) 序列,目标就是把 \(0\) 两侧的元素中 \(\pm1\) 个数调至相等。

注意到两种元素之差总是连续变化,因此只要维护前后缀中 \(\sum x\) 的最大最小值。

以最大化 \(\sum x\) 为例,手玩发现贪心策略一定是操作 \([-1,-1,-1]\)\([-1,1,-1]\),可以用 DFA 描述,最小化后用线段树维护区间信息即可。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
struct info {
	array<int,3> e,w;
	inline info operator+(const info& v) const {
		return {{v.e[e[0]],v.e[e[1]],v.e[e[2]]},{v.w[e[0]]+w[0],v.w[e[1]]+w[1],v.w[e[2]]+w[2]}};
	}
}	f[1<<19],g[1<<19],O={{0,1,2},{0,0,0}},X0={{0,0,1},{1,1,1}},X1={{1,2,1},{-1,-1,1}};
int n,N,a[MAXN];
int qry(int x,info*t) {
	info L=O,R=O;
	for(x+=N;x;x>>=1) x&1?L=t[x^1]+L:R=R+t[x^1];
	return L.w[0]+R.w[0];
}
void solve() {
	cin>>n,N=2<<__lg(n);
	for(int i=1,x;i<=n;++i) cin>>x,a[x]=i;
	for(int i=0;i<N;++i) {
		if(1<=i&&i<=n) f[i+N]=X0,g[i+N]=X1;
		else f[i+N]=g[i+N]=O;
	}
	for(int i=N-1;i;--i) f[i]=f[i<<1]+f[i<<1|1],g[i]=g[i<<1]+g[i<<1|1];
	for(int x=1;x<=n;++x) {
		cout<<(qry(a[x],f)>=0&&qry(a[x],g)>=0);
		int u=a[x]+N; f[u]=X1,g[u]=X0;
		for(u>>=1;u;u>>=1) f[u]=f[u<<1]+f[u<<1|1],g[u]=g[u<<1]+g[u<<1|1];
	}
	cout<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



S. [QOJ10331] Shuffle and Max Bracket Score (2.5)

Problem Link

首先原问题可以贪心,看成 \([1,2k-1]\) 中至少要选 \(k\) 个元素,即选 \(a_1\) 后,每加入两个 \(a\) 中元素就弹出当前最大值。

转成 \([a_i\ge k]\) 的 01 序列,我们只关心堆中 \(1\) 个数 \(c\),发现在 \(c\) 不与 \(0\)\(\max\) 的情况下,加入两个元素后 \(c\) 会变成 \(c-1,c,c+1\),且 \(c\) 不变有两种方案。

那么可以看成每加入一个元素选择让 \(c\) 变成 \(c\pm 0.5\),这样一个序列就变成了一条 \(\pm 1\) 折线,而此时的答案只和到达的最低点有关。

用反射容斥描述答案,转化式子后不难 \(\mathcal O(1)\) 计算。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,MOD=998244353;
ll fac[MAXN],ifac[MAXN],su[MAXN],ans=0;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll C(int x,int y) { return y<0||y>x?0:fac[x]*ifac[y]%MOD*ifac[x-y]%MOD; }
int n,m,a[MAXN];
int qry(int l,int r) {
	if(l<0) l=l&1;
	if(r>2*m) r=2*m-(r&1);
	return (su[r]+MOD+(l>1?MOD-su[l-2]:0))%MOD;
}
ll calc(int k) {
	if(!k) return 0;
	int l=max(0,m-k)+1,r=m;
	ll z=((r-l+1)*C(2*m,k)-qry(2*l+k,2*r+k))%MOD;
	return z;
}
ll solve(int k) {
	ll z=0;
	if(1<=k&&k<=n-1) z=(z+C(n-2,k-1)+2*calc(k-1))%MOD;
	if(k>=2) z=(z+C(n-2,k-2)+calc(k-2))%MOD;
	if(k<=n-2) z=(z+calc(k))%MOD;
	return z*fac[k]%MOD*fac[n-k]%MOD;
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n,m=n-1,n*=2;
	for(int i=fac[0]=1;i<=n;++i) fac[i]=fac[i-1]*i%MOD;
	ifac[n]=ksm(fac[n]);
	for(int i=n;i;--i) ifac[i-1]=ifac[i]*i%MOD;
	for(int i=1;i<=n;++i) cin>>a[i];
	sort(a+1,a+n+1,greater<>());
	for(int i=0;i<=2*m;++i) su[i]=((i>1?su[i-2]:0)+C(2*m,i))%MOD;
	for(int i=1;i<=n;++i) ans=(ans+(a[i]-a[i+1])*solve(i))%MOD;
	cout<<ans*ifac[n]%MOD<<"\n";
	return 0;
}



*T. [QOJ10706] Red-Blue MST (7.5)

Problem Link

首先假设所有边权以及边权之差互不相同,可以通过微扰做到。

根据经典结论,合法的 \(k\) 是一段区间,且答案有凸性。

观察 wqs 二分的过程,发现我们会把红边权值减去 \(\delta\) 后求 MST,在 \(\delta\) 取遍 \([-\infty,+\infty]\) 时能得到所有答案。

考虑增加 \(\delta\) 导致树结构改变,此时必是把一条蓝边变成了红边,且 \(\delta\) 更大时也会用该红边代替蓝边。

因此我们只要求 \(k_{\min}\to k_{\max}\) 的过程中每条红边代替了哪条蓝边。

把红边从小到大排序,每次加入一条红边时就会弹出环上最大的蓝边,容易证明该做法的正确性。

可以直接 LCT 维护,也能整体二分,加入 \([l,mid]\) 的蓝边后还能加入的红边就会代替 \([mid+1,r]\) 的蓝边,否则会代替 \([l,mid]\) 的蓝边,可撤销并查集维护即可。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef basic_string<array<int,3>> ve;
const int MAXN=1e5+5;
int n,m,t,rk[MAXN],dsu[MAXN],st[MAXN],tp;
int find(int x) { for(;dsu[x]^x;x=dsu[dsu[x]]); return x; }
bool link(int x,int y) {
	x=find(x),y=find(y);
	if(x==y) return 0;
	if(rk[x]<rk[y]) swap(x,y);
	st[++tp]=y<<1|(rk[x]==rk[y]);
	dsu[y]=x,rk[x]+=st[tp]&1;
	return 1;
}
void init(int o) {
	for(int y;tp>o;--tp) y=st[tp]>>1,rk[dsu[y]]-=st[tp]&1,dsu[y]=y;
}
ll f[MAXN];
void solve() {
	cin>>n>>m,t=tp=0,fill(f,f+n,-1);
	for(int i=1;i<=n;++i) rk[i]=0,dsu[i]=i;
	ve R,B,X,Y;
	for(int i=1,u,v,w;i<=m;++i) {
		char o; cin>>u>>v>>w>>o;
		(o=='R'?R:B).push_back({w,u,v});
	}
	sort(R.begin(),R.end()),sort(B.begin(),B.end());
	ll ans=0;
	for(auto e:R) if(link(e[1],e[2])) X+=e;
	R.swap(X),init(0),X.clear();
	for(auto e:B) if(link(e[1],e[2])) ans+=e[0],Y+=e;
	B.swap(Y),Y.clear();
	for(auto e:R) {
		if(link(e[1],e[2])) ans+=e[0],++t,Y+=e;
		else X+=e;
	}
	R.swap(X),init(0),f[t]=ans;
	for(auto e:Y) link(e[1],e[2]);
	vector <int> d;
	auto cdq=[&](auto self,int l,int r,ve&Q) {
		if(Q.empty()) return ;
		if(l==r) return d.push_back(Q[0][0]-B[l][0]);
		int mid=(l+r)>>1,o1=tp; ve LQ,RQ;
		for(int i=l;i<=mid;++i) link(B[i][1],B[i][2]);
		int o2=tp;
		for(auto e:Q) (link(e[1],e[2])?RQ:LQ)+=e;
		init(o2),self(self,mid+1,r,RQ),init(o1);
		for(auto e:RQ) link(e[1],e[2]);
		self(self,l,mid,LQ),init(o1);
	};
	cdq(cdq,0,B.size()-1,R);
	sort(d.begin(),d.end());
	for(int z:d) f[t+1]=f[t]+z,++t;
	for(int i=0;i<n;++i) cout<<f[i]<<" \n"[i==n-1];
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



*U. [QOJ11105] Requiem for Qingyu (8)

Problem Link

首先翻转 \(S\) 然后建立 SAM,对于一个节点 \(u\),要求所有 \([i-\mathrm{len}(\mathrm{LCA}(u,p_i)),i]\) 覆盖 \([1,n]\)

注意到可能扩展的区间一定满足 \(p_i\)\(u\) 子树中,即 \(i\in\mathrm{endpos}(u)\)

朴素想法就是从上到下维护 \([1,n]\) 的覆盖情况,注意到 \(u\) 向下的时候只会分裂 \(\mathrm{endpos}(u)\) 并增加 \(\mathrm{endpos}(v)\) 中后缀的覆盖长度。

因此对于每个 \(i\in\mathrm{endpos}(u)\),我们只要维护 \(i\) 前方距离 \(i\) 最远的未覆盖元素 \(x\),其他元素在 \([x,i]\) 被覆盖时必定会被覆盖。

那么就可以线段树维护 \(\mathrm{endpos}(u)\),以及相邻两个 \(\mathrm{endpos}(u)\) 中元素之间最远的未覆盖元素距离,转移的时候树链剖分,轻儿子暴力从 \(\mathrm{endpos}(u)\) 中删除并计算信息,重儿子继承删除后的 \(\mathrm{endpos}(u)\)

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,MAXM=1e7+5;
int n,ls[MAXM],rs[MAXM],d[MAXM],w[MAXM],op[MAXM],st[MAXM],tp;
void init() {
	tp=n*60;
	for(int i=1;i<=tp;++i) ls[i]=rs[i]=d[i]=w[i]=0,st[i]=i;
}
int newp() { return st[tp--]; }
void delp(int &p) { st[++tp]=p,d[p]=w[p]=ls[p]=rs[p]=0,p=0; }
void build(int &p,int l=1,int r=n) {
	p=newp(),d[p]=1;
	if(l==r) return ;
	int mid=(l+r)>>1;
	build(ls[p],l,mid),build(rs[p],mid+1,r);
}
void psu(int p) { d[p]=max(d[ls[p]],d[rs[p]]); }
void adt(int p,int k) { if(p) d[p]=d[p]<=k?0:d[p],w[p]=max(w[p],k); }
void psd(int p) { if(w[p]) adt(ls[p],w[p]),adt(rs[p],w[p]),w[p]=0; }
void ins(int x,int v,int &p,int l=1,int r=n) {
	if(!p) p=newp();
	if(l==r) return d[p]=v,void();
	int mid=(l+r)>>1; psd(p);
	x<=mid?ins(x,v,ls[p],l,mid):ins(x,v,rs[p],mid+1,r);
	psu(p);
}
void chg(int x,int v,int p,int l=1,int r=n) {
	if(l==r) return d[p]=max(d[p],v),void();
	int mid=(l+r)>>1; psd(p);
	x<=mid?chg(x,v,ls[p],l,mid):chg(x,v,rs[p],mid+1,r);
	psu(p);
}
void ers(int x,int &p,int l=1,int r=n) {
	if(l==r) return delp(p);
	int mid=(l+r)>>1; psd(p);
	x<=mid?ers(x,ls[p],l,mid):ers(x,rs[p],mid+1,r);
	if(!ls[p]&&!rs[p]) return delp(p);
	psu(p);
}
int qry(int ul,int ur,int p,int l=1,int r=n) {
	if(ul>ur||!d[p]) return 0;
	if(l==r) return d[p]+ur-r;
	int mid=(l+r)>>1,x=0; psd(p);
	if(ul<=mid) x=qry(ul,ur,ls[p],l,mid);
	if(mid<ur&&!x) x=qry(ul,ur,rs[p],mid+1,r);
	return x;
}
int nxt(int x,int p,int l=1,int r=n) {
	if(x>n||!p) return n+1;
	if(l==r) return l;
	int mid=(l+r)>>1,o=n+1; psd(p);
	if(x<=mid) o=nxt(x,ls[p],l,mid);
	return o>n?nxt(x,rs[p],mid+1,r):o;
}
int tot,lst,ch[MAXN][26],fa[MAXN],len[MAXN],ed[MAXN];
int ins(int c) {
	int u=++tot,p=lst;
	len[u]=len[p]+1;
	for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=u;
	if(!p) fa[u]=1;
	else {
		int q=ch[p][c];
		if(len[q]==len[p]+1) fa[u]=q;
		else {
			int r=++tot;
			fa[r]=fa[q],len[r]=len[p]+1,fa[u]=fa[q]=r;
			memcpy(ch[r],ch[q],sizeof(ch[r]));
			for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=r;
		}
	}
	return lst=u;
}
int siz[MAXN],hson[MAXN],dfn[MAXN],dcnt,rk[MAXN];
vector <int> G[MAXN];
ll ans,cnt[MAXN];
void dfs1(int u) {
	dfn[u]=dcnt;
	if(ed[u]) siz[u]=1,rk[++dcnt]=ed[u];
	for(int v:G[u]) {
		dfs1(v),siz[u]+=siz[v],cnt[u]+=cnt[v];
		if(!hson[u]||siz[v]>siz[hson[u]]) hson[u]=v;
	}
	cnt[u]+=1ll*siz[u]*(len[u]-len[fa[u]]);
}
int rt[MAXN],q[MAXN];
void dfs2(int u) {
	if(d[rt[u]]<=len[u]) {
		ans+=1ll*(len[u]-d[rt[u]]+1)*siz[u];
		for(int v:G[u]) ans+=cnt[v];
		return ;
	}
	adt(rt[u],len[u]);
	auto del=[&](int x) {
		int l=qry(x,x,rt[u]),r=nxt(x+1,rt[u]);
		if(l&&r<=n) chg(r,r-x+l,rt[u]);
		return ers(x,rt[u]),l&&r>n;
	};
	if(ed[u]&&del(ed[u])) return ;
	for(int v:G[u]) if(v^hson[u]) {
		for(int i=1;i<=siz[v];++i) q[i]=rk[dfn[v]+i];
		int ct=siz[v]; sort(q+1,q+ct+1);
		bool ok=!qry(q[ct]+1,n,rt[u]),kl=0;
		for(int i=ct;i;--i) {
			int id=qry(q[i-1]+1,q[i],rt[u]);
			kl|=del(q[i]);
			if(ok) ins(q[i],id,rt[v]);
		}
		if(ok) dfs2(v);
		if(kl) return ;
	}
	if(hson[u]) rt[hson[u]]=rt[u],dfs2(hson[u]);
}
void solve() {
	string s;
	cin>>s,n=s.size(),init(),tot=lst=1,ans=dcnt=0;
	for(int i=n-1;~i;--i) ed[ins(s[i]-'a')]=n-i;
	for(int i=2;i<=tot;++i) G[fa[i]].push_back(i);
	dfs1(1),build(rt[1]),dfs2(1),cout<<ans<<"\n";
	for(int i=1;i<=tot;++i) {
		rt[i]=fa[i]=len[i]=ed[i]=siz[i]=hson[i]=cnt[i]=0;
		G[i].clear(),memset(ch[i],0,sizeof(ch[i]));
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



V. [QOJ10746] Las Vegas (3)

Problem Link

每个游戏只能让合法且出价最高的前两个人或者 \(m+1\) 赢。

枚举 \(m+1\) 的获胜次数 \(k\),不难看成带权匹配问题,费用流建模即可。

要求 \(m+1\to T\) 的边流量 \(\ge k\),连容量 \(k\) 权值 \(+\infty\) 的边和容量 \(\infty\) 权值 \(0\) 的边即可。

时间复杂度 \(\mathcal O(n^2(n+m)^2)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
namespace F {
const int MAXV=205,MAXE=3e5+5,inf=1e9;
struct Edge { int v,e,f; ll w; } G[MAXE];
int S,T,ec=1,hd[MAXV],pre[MAXV]; ll dis[MAXV];
bool inq[MAXV];
void init() { ec=1,memset(hd,0,sizeof(hd)); }
void adde(int u,int v,int f,ll w) { G[++ec]={v,hd[u],f,w},hd[u]=ec; }
void link(int u,int v,int f,ll w) { adde(u,v,f,w),adde(v,u,0,-w); }
bool SPFA() {
	memset(dis,0x3f,sizeof(dis));
	memset(pre,0,sizeof(pre));
	memset(inq,false,sizeof(inq));
	queue <int> Q; Q.push(S),inq[S]=true,dis[S]=0;
	while(Q.size()) {
		int u=Q.front(); Q.pop(),inq[u]=false;
		for(int i=hd[u],v;i;i=G[i].e) if(G[i].f&&dis[v=G[i].v]>dis[u]+G[i].w) {
			dis[v]=dis[u]+G[i].w,pre[v]=i;
			if(!inq[v]) Q.push(v),inq[v]=true;
		}
	}
	return pre[T];
}
array<ll,2> ssp() {
	ll f=0,c=0;
	while(SPFA()) {
		int g=inf;
		for(int u=T;u!=S;u=G[pre[u]^1].v) g=min(g,G[pre[u]].f);
		f+=g,c+=g*dis[T];
		for(int u=T;u!=S;u=G[pre[u]^1].v) G[pre[u]].f-=g,G[pre[u]^1].f+=g;
	}
	return {f,c};
}
}
const int inf=1e9;
const ll INF=1e14;
ll ans;
int n,m,a[55],b[55],v1[55],v2[55],e1[55],e2[55],e3[55],w[55];
void calc(int k) {
	F::init();
	int s=F::S=n+m+3,t=F::T=s+1;
	for(int i=1;i<=m;++i) F::link(i,t,k,0);
	F::link(m+1,t,k,-INF);
	F::link(m+2,t,n,0);
	for(int i=1;i<=n;++i) {
		F::link(s,m+2+i,1,0);
		F::link(m+2+i,a[i],1,0),e1[i]=F::ec;
		F::link(m+2+i,b[i],1,v2[i]),e2[i]=F::ec;
		F::link(m+2+i,m+1,1,v1[i]),e3[i]=F::ec;
	}
	auto o=F::ssp(); o[1]+=k*INF;
	if(o[0]==n&&0<=o[1]&&o[1]<ans) {
		ans=o[1];
		for(int i=1;i<=n;++i) {
			if(F::G[e1[i]].f) w[i]=0;
			else if(F::G[e2[i]].f) w[i]=v2[i];
			else w[i]=v1[i];
		}
	}
}
void solve() {
	cin>>n>>m;
	for(int i=1;i<=n;++i) {
		a[i]=b[i]=m+2,v1[i]=v2[i]=0;
		map<int,int>t;
		for(int j=1,x;j<=m;++j) {
			cin>>x;
			if(x) t[x]=(t[x]?-1:j);
		}
		if(t.empty()) { v1[i]=1; continue; }
		int z=t.rbegin()->first+1,v=0,o=0;
		for(auto j=t.rbegin();j!=t.rend()&&o<2;v=j->first,++j) {
			if(j->first+1<v) z=j->first+1;
			if(j->second>0) {
				if(!o) a[i]=j->second,v1[i]=z,v2[i]=j->first;
				else b[i]=j->second;
				++o;
			}
		}
		if(o==0) v1[i]=v>1?1:z;
	}
	ans=INF;
	for(int k=0;k<=n;++k) calc(k);
	for(int i=1;i<=n;++i) cout<<w[i]<<" \n"[i==n];
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _;  cin>>_;
	while(_--) solve();
	return 0;
}



*W. [QOJ10730] Sweet Sugar IV (8.5)

Problem Link

首先在平面 DAG 上判定 \(u\to v\) 是否可达,可以通过把每个点的出边按斜率分别递增递减排序后取出后序遍历,只有两种顺序中 \(v\) 都比 \(u\) 先出栈才合法。

那么这样求出两组顺序后,一个机器人的路径就变成了一条上升序列。

求带权和最大的 \(k\) 条 LIS 是经典问题,首先无权情况只要维护杨表前 \(k\) 行,而带权可以把权值为 \(w\) 的元素看成 \(w\) 个权值为 \(1\) 的元素,显然不会改变答案。

那么我们用 std::map 维护杨表即可。

现在问题是我们多次询问前缀中值 \(\le w\) 的元素构成的杨表形态,归纳发现此时的杨表就是原杨表删掉 \(>w\) 的元素得到的杨表,所以只要用树状数组维护杨表的每一行元素。

时间复杂度 \(\mathcal O((nm+q)k\log nm)\)

代码:

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
using namespace std;
const int MAXN=1e6+5;
vector <int> A[MAXN],B[MAXN],C[MAXN];
int H,W,n,m,q,ct;
array<int,3> a[MAXN];
void dfs1(int x,int y) {
	if(x>H||y>W||C[x][y]||A[x][y]) return ;
	dfs1(x+1,y),dfs1(x,y+1),A[x][y]=++ct;
}
void dfs2(int x,int y) {
	if(x>H||y>W||C[x][y]||B[x][y]) return ;
	dfs2(x,y+1),dfs2(x+1,y),B[x][y]=++ct;
}
vector <array<int,3>> qy[MAXN];
ll ans[MAXN];
struct BIT {
	ll tr[MAXN],s;
	void add(int x,ll v) { for(;x<=ct;x+=x&-x) tr[x]+=v; }
	ll qry(int x) { for(s=0;x;x&=x-1) s+=tr[x]; return s; }
}	T[5];
map <int,ll> f[5];
void ins(int k,int x,ll w) {
	if(k==5) return ;
	T[k].add(x,w),f[k][x]+=w;
	auto i=f[k].upper_bound(x);
	for(;i!=f[k].end()&&w;i=f[k].erase(i)) {
		if(i->se>w) { T[k].add(i->fi,-w),i->se-=w,ins(k+1,i->fi,w); break; }
		T[k].add(i->fi,-i->se),ins(k+1,i->fi,i->se),w-=i->se;
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>H>>W>>m>>n>>q;
	for(int i=1;i<=H;++i) A[i]=B[i]=C[i]=vector<int>(W+1);
	for(int i=1,lx,ly,rx,ry;i<=m;++i) {
		cin>>lx>>ly>>rx>>ry;
		for(int x=lx;x<=rx;++x) for(int y=ly;y<=ry;++y) C[x][y]=1;
	}
	dfs1(1,1),ct=0,dfs2(1,1);
	for(int i=1,x,y,z;i<=n;++i) cin>>x>>y>>z,a[i]={A[x][y],B[x][y],z};
	sort(a+1,a+n+1);
	for(int i=1,x,y,k,p;i<=q;++i) {
		cin>>x>>y>>k,p=lower_bound(a+1,a+n+1,array<int,3>{A[x][y]+1,0,0})-a-1;
		qy[p].push_back({B[x][y],k,i});
	}
	for(int i=1;i<=n;++i) {
		ins(0,a[i][1],a[i][2]);
		for(auto o:qy[i]) for(int j=0;j<o[1];++j) ans[o[2]]+=T[j].qry(o[0]);
	}
	for(int i=1;i<=q;++i) cout<<ans[i]<<"\n";
	return 0;
}



*X. [QOJ10974] Edge Coloring Problem (8)

Problem Link

首先下界是 \(\dfrac{n(n-3)}{2\lfloor n/2\rfloor}\)\(2\mid n\) 时为 \(n-3\) 否则为 \(n-2\)

如果 \(n\) 是奇数,加入一个新点转成 \(n+1\) 的情况,此时把 \(n+1\) 在补图中随便插入一个环中即可。

\(n\) 为偶数时,如果补图所有环长都是偶数,那么把每个点间隔染色变成二分图,原图看成左右部点的完全图拼上两侧点间的正则二分图,每个点度数为 \(\dfrac n2-2\)

如果 \(\dfrac n2\) 为偶数,那么对两侧点的完全图可以分别划分为 \(\dfrac n2-1\) 个完美匹配,两侧并行,中间的正则二分图直接构造一组边染色就能得到总代价 \(n-3\) 的方案。

然后考虑 \(\dfrac n2\) 为奇数的情况,加入一个虚拟点后构造完全图划分,此时每个划分中会有一个点多出来。

注意到我们可以选择左侧的每个划分对应右侧的哪个划分。

此时我们构造中间的二分图边染色,取出某种颜色的边,这是一个完美匹配,对于其中每条边 \((x,y)\),我们把左侧不包含 \(x\) 的完美匹配和右侧不包含 \(y\) 的完美匹配放在一起并加入 \((x,y)\) 即可。

如果有奇环,那么我们把其中两个点放到左侧,对于下一个奇环则把两个点放到右侧。

此时左右两侧分别有 \(k\) 条被删掉的边 \((l_1,l_2)\sim (l_{2k-1},l_{2k})\) 以及 \((r_1,r_2)\sim (r_{2k-1},r_{2k})\)

加入这些边并删掉所有 \((l_i,r_i)\),然后用类似刚才的方法构造一组解。

调整时,我们构造方法使得所有的 \((l_{2i-1},l_{2i}),(r_{2i-1},r_{2i})\) 全部同色,然后把这些边换成 \((l_i,r_i)\) 即可。

具体实现时,首先要对两侧的点重标号使得所有 \((l_{2i-1},l_{2i}),(r_{2i-1},r_{2i})\) 分别同色,然后如果 \(\dfrac n2\) 为奇数,我们要选一对不属于 \(L,R\) 点集的匹配边来匹配两侧的划分。

时间复杂度 \(\mathcal O(n^3)\)

代码:

#include<bits/stdc++.h>
using namespace std;
int a[305][305],b[305][305];
basic_string <int> G[305];
basic_string <array<int,2>> E[305];
bool vis[305];
void solK(int n) {
	for(int i=0;i<n;++i) {
		for(int j=1;j<=n/2;++j) E[i].push_back({(i+j)%n,(i+n-j)%n});
		E[i].push_back({i,n});
	}
}
int s[305],t[305],g[305][305],q[305],w[305];
void add(int u,int v) {
	int x=1,y=1;
	for(;g[u][x];++x);
	for(;g[v][y];++y);
	g[u][x]=v,g[v][y]=u;
	if(x==y) return ;
	for(int o=v,z=y;o;o=g[o][z],z^=x^y) swap(g[o][x],g[o][y]);
}
void solve(int n) {
	for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) if(!a[i][j]) G[i]+=j,G[j]+=i;
	basic_string <int> L,R,X,Y;
	for(int i=1,o=0;i<=n;++i) if(!vis[i]) {
		basic_string <int> c;
		for(int x=i,y=G[x][0],z;!vis[x];z=G[x][0]+G[x][1]-y,y=x,x=z) vis[x]=1,c+=x;
		int k=c.size();
		if(k&1) {
			if(o) X+=c[0],X+=c[1];
			else Y+=c[0],Y+=c[1];
			for(int j=2;j<k;++j) (j%2==o?L:R)+=c[j];
			o^=1;
		} else for(int j=0;j<k;++j) (j&1?L:R)+=c[j];
	}
	L=X+L,R=Y+R;
	int k=X.size()/2,m=n/2,h=m+(m&1);
	for(int i=0;i<2*k;++i) a[X[i]][Y[i]]=a[Y[i]][X[i]]=2;
	for(int i=0;i<m;++i) for(int j=0;j<m;++j) if(a[L[i]][R[j]]==1) add(L[i],R[j]);
	for(int i=0;i<m;++i) for(int j=1;j<m-1-(m&1);++j) b[L[i]][g[L[i]][j]]=b[g[L[i]][j]][L[i]]=j;
	for(int i=0;i<m;++i) q[L[i]]=g[L[i]][m-2];
	solK(h-1);
	for(int i=0;i<h/2;++i) for(int o:{0,1}) if(i*2+o<m) {
		s[E[h-2][i][o]]=L[i<<1|o],t[E[h-2][i][o]]=R[i<<1|o];
	}
	if(m&1) {
		memset(vis,0,sizeof(vis));
		for(int i=2*k;i<m;++i) vis[R[i]]=true;
		for(int i=2*k;i<m;++i) if(vis[q[L[i]]]) {
			swap(s[find(s,s+m,L[i])-s],s[h-2]);
			swap(t[find(t,t+m,q[L[i]])-t],t[h-2]);
			break;
		}
		for(int c=0;c<m;++c) w[find(t,t+m,q[s[c]])-t]=c,b[s[c]][q[s[c]]]=b[q[s[c]]][s[c]]=c+m-1-(m&1);
	} else iota(w,w+h-1,0);
	for(int c=0;c<h-1;++c) for(auto e:E[c]) if(e[1]<m) {
		b[s[e[0]]][s[e[1]]]=b[s[e[1]]][s[e[0]]]=c+m-1-(m&1);
		b[t[e[0]]][t[e[1]]]=b[t[e[1]]][t[e[0]]]=w[c]+m-1-(m&1);
	}
	for(int i=0;i<2*k;i+=2) {
		b[X[i]][Y[i]]=b[Y[i]][X[i]]=b[X[i+1]][Y[i+1]]=b[Y[i+1]][X[i+1]]=b[X[i]][X[i+1]];
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int n; cin>>n;
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) {
		char o; cin>>o,a[i][j]=o-'0';
	}
	int x=0,y=0;
	if(n&1) {
		for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) if(!a[i][j]) x=i,y=j;
		a[x][y]=a[y][x]=1;
		for(int i=1;i<=n;++i) a[n+1][i]=a[i][n+1]=(i!=x&&i!=y);
	}
	solve(n+(n&1));
	if(n&1) a[x][y]=a[y][x]=0;
	cout<<n+(n&1)-3<<"\n";
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) cout<<(a[i][j]?b[i][j]:-1)<<" \n"[j==n];
	return 0;
}



Y. [QOJ10701] Suspicious Submissions (3.5)

Problem Link

把正反串分别建 Trie,枚举较长串,要求就是另一个串在两棵树上和当前串的 \(\mathrm{LCA}\) 深度和要大于某个值。

枚举在第一棵 Trie 上的 \(\mathrm{LCA}\) 深度,变成二维数点,注意到点数和询问数都是均摊 \(\mathcal O(\sum |S|)\) 的,可以通过。

注意我们不能考虑 \(\mathrm{lcp},\mathrm{lcs}\) 有相交的情况,只要限定另一个串的长度即可,此时依旧可以二维数点,一维限制长度,另一维限制反串 Trie 上的 dfn 序。

时间复杂度 \(\mathcal O(\sum |S|\log |S|)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5;
struct trie {
	int tr[MAXN][26],tot;
	void init() {
		memset(tr,0,sizeof(tr[0])*(tot+1)),tot=0;
	}
	void ins(const string&o) {
		int p=0;
		for(auto c:o) {
			if(!tr[p][c-'A']) tr[p][c-'A']=++tot;
			p=tr[p][c-'A'];
		}
	}
}	a,b;
int n,dfn[MAXN],efn[MAXN],fa[MAXN],dcnt,len[MAXN];
struct BIT {
	int tr[MAXN],s,st[MAXN],tp;
	void add(int x,int v) { for(st[++tp]=x;x<=dcnt;x+=x&-x) tr[x]+=v; }
	int qry(int x) { for(s=0;x;x&=x-1) s+=tr[x]; return s; }
	void init() { while(tp) for(int x=st[tp--];x<=dcnt;x+=x&-x) tr[x]=0; }
}	T;
string s[MAXN];
basic_string <array<int,3>> qy[MAXN];
void dfs(int u) {
	dfn[u]=++dcnt;
	for(int v:b.tr[u]) if(v) fa[v]=u,len[v]=len[u]+1,dfs(v);
	efn[u]=dcnt;
}
int k,c[MAXN],d[MAXN];
void solve() {
	cin>>n>>k,a.init(),b.init();
	for(int i=1;i<=n;++i) cin>>s[i];
	sort(s+1,s+n+1,[&](auto&x,auto&y){ return x.size()<y.size(); });
	for(int i=1;i<=n;++i) {
		a.ins(s[i]),reverse(s[i].begin(),s[i].end());
		b.ins(s[i]),reverse(s[i].begin(),s[i].end());
	}
	dcnt=0,dfs(0);
	ll ans=0;
	for(int i=1;i<=n;++i) {
		int q=s[i].size();
		for(int j=0;j<q;++j) {
			c[j+1]=a.tr[c[j]][s[i][j]-'A'];
			d[j+1]=b.tr[d[j]][s[i][q-1-j]-'A'];
		}
		for(int j=0;j<=q;++j) {
			qy[c[j]].push_back({d[q],d[max(0,q-j-k)],q-k});
			if(j<q) qy[c[j+1]].push_back({-1,d[max(0,q-j-k)],q-k});
		}
	}
	for(int u=0;u<=a.tot;++u) {
		int i=0;
		for(auto o:qy[u]) {
			if(~o[1]) {
				for(;i<(int)qy[u].size()&&(qy[u][i][0]<0||len[qy[u][i][0]]<o[2]);++i) {
					if(~qy[u][i][0]) T.add(dfn[qy[u][i][0]],-1);
				}
				ans+=(~o[0]?1:-1)*(T.qry(efn[o[1]])-T.qry(dfn[o[1]]-1));
			}
			if(~o[0]) T.add(dfn[o[0]],1);
		}
		T.init(),qy[u].clear();
	}
	cout<<ans<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



Z. [QOJ11108] Nocturne without a Moon (4)

Problem Link

首先枚举前两个元素约分后的比例 \(\dfrac xy\),则要统计的就是形如 \([xu,yu,yv,xv]\) 的子序列,其中 \(\gcd(x,y)=1\)

考虑根号分治,由于 \(\max(x,y)\max(u,v)\le n\),因此可以按 \(B\) 分治,如果 \(\max(x,y)\le B\) ,暴力枚举 \((x,y)\) 变成 \(p_{yu}<p_{yv}\) 的数对个数,容易做到 \(\mathcal O(nB+\sum\frac nx+\frac ny)=\mathcal O(nB)\)

如果 \(\max(u,v)\le \dfrac nB\),枚举 \((u,v)\) 然后算二维偏序个数可以做到 \(\mathcal O(\frac nB\times n\log n)\)

但此时没有处理 \(\gcd(x,y)=1\) 的限制,枚举 \(d\mid \gcd(x,y)\) 然后莫比乌斯反演处理掉,复杂度 \(\mathcal O(\sum_d\frac {(n/d)^2\log n}B)=\mathcal O(\frac{n^2\log n}B)\)

取 $B=\sqrt{\dfrac n{\log n}} $ 平衡复杂度,注意计数第一部分时要钦定 \(\max(u,v)>B\)

时间复杂度 \(\mathcal O(n\sqrt {n\log n})\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e4+5;
int n,a[MAXN],b[MAXN],mu[MAXN],B,Q;
bool isc[MAXN];
int id[MAXN],m,rk[MAXN],c[MAXN],w[MAXN],t[MAXN];
ll ans=0,sum;
array<int,2>d[MAXN];
void cdq(int l,int r) {
	if(l==r) return ;
	int mid=(l+r)>>1;
	cdq(l,mid),cdq(mid+1,r);
	int i=l,j=mid+1,k=0;
	while(i<=mid||j<=r) {
		if(j>r||(i<=mid&&w[i]<w[j])) t[++k]=w[i++];
		else t[++k]=w[j++],sum+=mid-i+1;
	}
	copy(t+1,t+k+1,w+l);
}
void solve(int k) {
	sum=0;
	for(int u=1;u<=Q;++u) for(int v=1;v<=Q;++v) if(u!=v) {
		m=0;
		for(int x=k,h=n/max(u,v);x<=h;x+=k) {
			if(b[x*u]<b[x*v]) d[++m]={b[x*u],b[x*v]};
		}
		if(m<2) continue;
		stable_sort(d+1,d+m+1);
		for(int i=1;i<=m;++i) w[i]=d[i][1];
		cdq(1,m);
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n,mu[1]=1;
	if(n<4) return cout<<"0\n",0;
	Q=sqrt(n/log2(n)),B=n/(Q+1);
	for(int i=1;i<=n;++i) cin>>a[i],b[a[i]]=i;
	vector <int> pr;
	for(int i=2;i<=n;++i) {
		if(!isc[i]) mu[i]=-1,pr.push_back(i);
		for(int p:pr) {
			if(i*p>n) break;
			mu[i*p]=-mu[i],isc[i*p]=true;
			if(i%p==0) { mu[i*p]=0; break; }
		}
	}
	for(int x=1;x<=B;++x) {
		m=0;
		for(int i=1;i<=n/x;++i) id[++m]=b[i*x];
		sort(id+1,id+m+1);
		for(int i=1;i<=m;++i) rk[a[id[i]]/x]=i;
		for(int y=1;y<=B;++y) if(x!=y&&__gcd(x,y)==1) {
			memset(c,0,(m+1)<<2);
			for(int i=1,u=n/max(x,y);i<=u;++i) {
				c[rk[i]]=(b[i*x]>b[i*y]?1:-1);
			}
			for(int i=1,s[2]={0,0};i<=m;++i) {
				if(c[i]>0) ++s[a[id[i]]>x*Q];
				else if(c[i]<0) ans+=s[1]+(a[id[i]]>x*Q?s[0]:0);
			}
		}
	}
	for(int k=1;k<=n;++k) if(mu[k]) solve(k),ans+=mu[k]*sum;
	cout<<ans<<"\n";
	return 0;
}
posted @ 2025-11-26 19:52  DaiRuiChen007  阅读(75)  评论(0)    收藏  举报