【比赛记录】2025CSP+NOIP 冲刺模拟赛合集Ⅵ

11.20 HZOJ NOIP2025模拟赛12

A B C D Sum Rank
66 20 66 12 164 18/34

A. 虫群之心

注意到 \(p-m\) 很小,于是 \(m!=\frac{(p-1)!}{\prod_{i=m+1}^{p-1}i}\),分子由威尔逊定理或打表可得出等于 \(p-1\)

为什么不打表为什么不打表为什么不打表为什么不打表为什么不打表为什么不打表为什么不打表为什么不打表

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
int T,m,p;
il int qpow(int x,int y,int p){
	int res=1;
	while(y){
		if(y&1){
			res=res*x%p;
		}
		x=x*x%p,y>>=1;
	}
	return res;
}
int main(){
	freopen("factorial.in","r",stdin);
	freopen("factorial.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>m>>p;
		if(m>=p){
			cout<<0<<'\n';
		}else{
			int res=p-1;
			while(m!=p-1){
				res=res*qpow(++m,p-2,p)%p;
			}
			cout<<res<<'\n';
		}
	}
	return 0;
}
}
signed main(){return asbt::main();}

B. 喝醉的兔子

对于每一个查询 \(x\),由于我们最后要走到 \(kn,k\in\mathbb{Z}\),考虑同余最短路,初值为 \(dis_{(x+i)\bmod n}=0,i\in[L,R]\),对于每个 \(u\) 有边 \(u\to(du+i)\bmod n,i\in[L,R]\),bfs 即可,时间复杂度 \(O(Tqn^2)\)

但这样直接建边空间是 \(O(n^2)\) 的。考虑 bfs 过程中每个点只有在第一次被更新时是有效的,而每个点的后继结点都是一段区间,于是我们可以用并查集来将已经更新过的区间覆盖掉,更新时跳并查集,空间复杂度线性。同时可以发现,此时每个点只会被更新一次,时间复杂度也来到了 \(O(Tqn)\)

考虑进一步优化,所有询问的终点都是 \(0\),因此可以建反图,从 \(0\) 开始跑一遍 bfs 即可。但此时我们发现难以使用上面那个并查集的优化,因为在反图上每个点的后继结点并不是一段区间。我们来再次审视此时的连边:

对于所有 \(x\),如果 \(u\texttt{ s.t.}\exist i\in[L,R],(du+i)\bmod n=x\) 则有边 \(x\to u\)。将这个式子变形:

\[du\equiv x-i\pmod{n}\\ \Rightarrow du+nt=x-i \]

我们发现此时虽然 \(u\) 不是一个区间,但 \(x-i\) 是一个区间。于是我们对于每个 \(x-i\),扩欧求出有哪些 \(u\) 满足这个式子,挂一个 vector 即可。

查询时需要查的实际上是 \(\min_{i=L}^{R}\{dis_{(x+i)\bmod n}\}\),ST 表维护一下即可。总时间复杂度 \(O(Tn\log n)\),轻微卡常。

Code
#include<bits/stdc++.h>
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e6+5,inf=1e9;
int T,n,d,l,r,m,fa[maxn],dep[maxn];
int q[maxn],hd,tl,st[21][maxn];
vector<int> vc[maxn];
il int exgcd(int a,int b,int &x,int &y){
	if(!b){
		x=1,y=0;
		return a;
	}
	int d=exgcd(b,a%b,x,y),t=x;
	x=y,y=t-a/b*y;
	return d;
}
il int find(int x){
	return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void push(int l,int r,int w){
	for(int i=find(l);i<=r;i=find(i)){
		for(int u:vc[i]){
			dep[u]=w,q[++tl]=u;
		}
		fa[i]=find(i+1);
	} 
}
il void push(int x,int w){
	int ll=(x-r+n)%n,rr=(x-l+n)%n;
	if(ll<=rr){
		push(ll,rr,w);
	}else{
		push(ll,n-1,w),push(0,rr,w);
	}
}
il int query(int l,int r){
	int p=__lg(r-l+1);
	return min(st[p][l],st[p][r-(1<<p)+1]);
}
int main(){
	freopen("calculate.in","r",stdin);
	freopen("calculate.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>d>>l>>r>>m;
		for(int i=0;i<=n;i++){
			fa[i]=i,dep[i]=inf,vc[i].clear();
		}
		for(int i=1;i<=n;i++){
			int x,y,g=exgcd(d,n,x,y);
			if(i%g){
				fa[i]=i+1;
			}else{
//				cout<<i<<": ";
				y=n/g;
				x=(x*1ll*(i/g)%y+y)%y;
				if(x==0){
					x=y;
				}
				while(x<n){
//					cout<<x<<' ';
					vc[i%n].pb(x);
					x+=y;
				}
//				cout<<'\n';
			}
		}
		hd=1,tl=0;
		q[++tl]=0,dep[0]=0;
		while(hd<=tl){
			int u=q[hd++];
			push(u,dep[u]+1);
		}
		for(int i=0;i<n;i++){
			st[0][i]=dep[i];
		}
		for(int j=1;j<=19;j++){
			for(int i=0;i+(1<<j)-1<n;i++){
				st[j][i]=min(st[j-1][i],st[j-1][i+(1<<(j-1))]);
			}
		}
		while(m--){
			int x;
			cin>>x;
			int ll=(x+l)%n,rr=(x+r)%n,res;
			if(ll<=rr){
				res=query(ll,rr);
			}else{
				res=min(query(ll,n-1),query(0,rr));
			}
			cout<<(res>n+5?-1:res)<<'\n';
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

\(x\to u\) 的连边实际上可以枚举 \(u\)\(x-i\) 做到线性,查询长为 \(R-L+1\) 的区间的最小值实际上可以用单调队列预处理,然后就能 \(O(Tn\alpha(n))\) 了。懒得写了

C. 盲盒流水线

直接猫树分治,类似「好吃的题目」,但由于要计数,只能在背包 DP 数组里记录体积恰好\(i\) 的最大价值和方案数。但这样查询时就不能直接 \(O(m)\) 合并了,时间复杂度 \(O(qm^2+nm\log n)\) 不是很能接受。解决方法也很好想,将两个需要合并的 DP 数组一个正着扫,另一个倒着扫,动态维护正着扫的那个当前的最大值及方案数即可。

为什么不取模为什么不取模为什么不取模为什么不取模为什么不取模为什么不取模为什么不取模为什么不取模

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e4+5,maxm=1e5+5,inf=1e9,mod=998244353;
il int pls(int x,int y){
	return x+y<mod?x+y:x+y-mod;
}
il void add(int &x,int y){
	x=pls(x,y);
}
il int mns(int x,int y){
	return x<y?x-y+mod:x-y;
}
il void sub(int &x,int y){
	x=mns(x,y);
}
int n,m,a[maxn],b[maxn],ans1[maxm],ans2[maxm];
int f1[maxn][505],g1[maxn][505];
int f2[maxn][505],g2[maxn][505];
struct{
	int l,r,w,id;
}c[maxm],d[maxm];
il void solve(int L,int R,int l,int r){
	if(l>r){
		return ;
	}
//	cerr<<L<<' '<<R<<' '<<l<<' '<<r<<'\n';
	int mid=(L+R)>>1;
	f1[mid+1][0]=0,g1[mid+1][0]=1;
	for(int i=1;i<=500;i++){
		f1[mid+1][i]=-inf,g1[mid+1][i]=0;
	}
	for(int i=mid;i>=L;i--){
		for(int j=0;j<=500;j++){
			f1[i][j]=f1[i+1][j],g1[i][j]=g1[i+1][j];
		}
		for(int j=500;j>=b[i];j--){
			if(f1[i][j-b[i]]+a[i]>f1[i][j]){
				f1[i][j]=f1[i][j-b[i]]+a[i];
				g1[i][j]=g1[i][j-b[i]];
			}else if(f1[i][j-b[i]]+a[i]==f1[i][j]){
				add(g1[i][j],g1[i][j-b[i]]);
			}
		}
	}
	f2[mid][0]=0,g2[mid][0]=1;
	for(int i=1;i<=500;i++){
		f2[mid][i]=-inf,g2[mid][i]=0;
	}
	for(int i=mid+1;i<=R;i++){
		for(int j=0;j<=500;j++){
			f2[i][j]=f2[i-1][j],g2[i][j]=g2[i-1][j];
		}
		for(int j=500;j>=b[i];j--){
			if(f2[i][j-b[i]]+a[i]>f2[i][j]){
				f2[i][j]=f2[i][j-b[i]]+a[i];
				g2[i][j]=g2[i][j-b[i]];
			}else if(f2[i][j-b[i]]+a[i]==f2[i][j]){
				add(g2[i][j],g2[i][j-b[i]]);
			}
		}
	}
	int p1=l,p2=r;
	for(int i=l;i<=r;i++){
		if(c[i].r<=mid){
			d[p1++]=c[i];
			continue;
		}
		if(c[i].l>mid){
			d[p2--]=c[i];
			continue;
		}
		int res1=-inf,res2=0;
		for(int j=c[i].w,k=0;~j;j--,k++){
			if(f2[c[i].r][k]>res1){
				res1=f2[c[i].r][k],res2=g2[c[i].r][k];
			}else if(f2[c[i].r][k]==res1){
				add(res2,g2[c[i].r][k]);
			}
			if(f1[c[i].l][j]+res1>ans1[c[i].id]){
				ans1[c[i].id]=f1[c[i].l][j]+res1;
				ans2[c[i].id]=g1[c[i].l][j]*1ll*res2%mod;
			}else if(f1[c[i].l][j]+res1==ans1[c[i].id]){
				ans2[c[i].id]=(ans2[c[i].id]+g1[c[i].l][j]*1ll*res2)%mod;
			}
		}
	}
	for(int i=l;i<p1;i++){
		c[i]=d[i];
	}
	for(int i=r;i>p2;i--){
		c[i]=d[i];
	}
	solve(L,mid,l,p1-1);
	solve(mid+1,R,p2+1,r);
}
int main(){
//	system("fc sample_knapsack4.out my.out");
	freopen("knapsack.in","r",stdin);
	freopen("knapsack.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i]>>b[i];
	}
	cin>>m;
	int cnt=0;
	for(int i=1,l,r,w;i<=m;i++){
		cin>>l>>r>>w;
		if(l==r){
			if(b[l]<=w){
				ans1[i]=a[l],ans2[i]=1;
			}else{
				ans1[i]=ans2[i]=0;
			}
		}else{
			c[++cnt]={l,r,w,i};
		}
	}
	solve(1,n,1,cnt);
	for(int i=1;i<=m;i++){
		if(!ans1[i]){
			ans2[i]=0;
		}
		cout<<ans1[i]<<' '<<ans2[i]<<'\n';
	}
	return 0;
}
}
signed main(){return asbt::main();}

D. 失落的帝国

首先考虑 \(m=n-1\),即不用考虑 mst 的限制,显然贪心,枚举 \(i\in[1,m]\),将所有 \(l_j\le i\)\(r_j\) 最小的 \(j\) 的边权赋成 \(i\) 即可。

考虑一般情况,我们希望用 mst 这个条件对 \(l_j,r_j\) 进行一些限制,然后再用上面的贪心。考虑一条非树边 \(i\),树上 \(u_i\)\(v_i\) 的路径上的最大边权应该 \(>w_i\)。记这条路径上的边为 \(k\),我们直接令 \(l_i\gets\max(l_i,l_k+1),r_k\gets\min(r_k,r_i-1)\)。显然这样不会将有节情况判成无解,且再用上面那个贪心就能满足 mst 的限制。

考虑树剖,我们要满足的操作就是求区间 \(\max\) 和区间取 \(\min\),用 ST 表和反向 ST 表即可。时间复杂度线性对数。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace asbt{
const int maxn=5e5+5;
int T,n,m,fa[maxn],dep[maxn];
int sz[maxn],hes[maxn],top[maxn];
int dfn[maxn],cnt,stk[maxn],c[maxn];
int st1[maxn][20],st2[maxn][20];
vector<int> e[maxn],b[maxn];
struct{
	int u,v,l,r;
}a[maxn];
il void dfs1(int u){
	sz[u]=1;
	int mxs=0;
	for(int v:e[u]){
		if(v==fa[u]){
			continue;
		}
		fa[v]=u;
		dep[v]=dep[u]+1;
		dfs1(v);
		sz[u]+=sz[v];
		if(mxs<sz[v]){
			mxs=sz[v],hes[u]=v;
		}
	}
}
il void dfs2(int u){
	if(!top[u]){
		top[u]=u;
	}
	dfn[u]=++cnt,stk[cnt]=u;
	if(hes[u]){
		top[hes[u]]=top[u];
		dfs2(hes[u]);
	}
	for(int v:e[u]){
		if(v==fa[u]||v==hes[u]){
			continue;
		}
		dfs2(v);
	}
}
il int qry1(int l,int r){
	if(l>r){
		return 0;
	}
	int p=__lg(r-l+1);
	return max(st1[l][p],st1[r-(1<<p)+1][p]);
}
il void upd2(int l,int r,int x){
	if(l>r){
		return ;
	}
	int p=__lg(r-l+1);
	st2[l][p]=min(st2[l][p],x);
	st2[r-(1<<p)+1][p]=min(st2[r-(1<<p)+1][p],x);
}
il int query(int u,int v){
	int res=0;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]){
			swap(u,v);
		}
		res=max(res,qry1(dfn[top[u]],dfn[u]));
		u=fa[top[u]];
	}
	if(dep[u]>dep[v]){
		swap(u,v);
	}
	return max(res,qry1(dfn[u]+1,dfn[v]));
}
il void upd(int u,int v,int x){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]){
			swap(u,v);
		}
		upd2(dfn[top[u]],dfn[u],x);
		u=fa[top[u]];
	}
	if(dep[u]>dep[v]){
		swap(u,v);
	}
	upd2(dfn[u]+1,dfn[v],x);
}
int main(){
	freopen("shi.in","r",stdin);
	freopen("shi.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>m;
		for(int i=1;i<=m;i++){
			cin>>a[i].u>>a[i].v>>a[i].l>>a[i].r;
			if(i<n){
				e[a[i].u].pb(a[i].v);
				e[a[i].v].pb(a[i].u);
			}
		}
		cnt=0,dfs1(1),dfs2(1);
//		for(int i=1;i<=n;i++){
//			cout<<i<<' '<<fa[i]<<' '<<dep[i]<<' '<<sz[i]<<' '<<hes[i]<<' '<<top[i]<<' '<<dfn[i]<<'\n';
//		}
		for(int i=1;i<n;i++){
			int u=a[i].u,v=a[i].v;
			st1[dfn[dep[u]>dep[v]?u:v]][0]=a[i].l;
			st2[dfn[dep[u]>dep[v]?u:v]][0]=a[i].r;
		}
		for(int j=1;j<=18;j++){
			for(int i=2;i+(1<<j)-1<=n;i++){
				st1[i][j]=max(st1[i][j-1],st1[i+(1<<(j-1))][j-1]);
				st2[i][j]=max(st2[i][j-1],st2[i+(1<<(j-1))][j-1]);
			}
		}
		for(int i=n;i<=m;i++){
			a[i].l=max(a[i].l,query(a[i].u,a[i].v)+1);
			upd(a[i].u,a[i].v,a[i].r-1);
		}
		for(int j=18;j;j--){
			for(int i=2;i+(1<<j)-1<=n;i++){
				st2[i][j-1]=min(st2[i][j-1],st2[i][j]);
				st2[i+(1<<(j-1))][j-1]=min(st2[i+(1<<(j-1))][j-1],st2[i][j]);
			}
		}
		for(int i=1;i<n;i++){
			int u=a[i].u,v=a[i].v;
			a[i].r=min(a[i].r,st2[dep[u]>dep[v]?dfn[u]:dfn[v]][0]);
		}
		priority_queue<pii> q;
		for(int i=1;i<=m;i++){
			b[a[i].l].pb(i);
//			cout<<a[i].u<<' '<<a[i].v<<' '<<a[i].l<<' '<<a[i].r<<'\n';
		}
		for(int i=1;i<=m;i++){
			for(int j:b[i]){
				q.push(mp(-a[j].r,j));
			}
			if(q.empty()||-q.top().fir<i){
				cout<<"NO\n";
				goto togo;
			}
			c[q.top().sec]=i,q.pop();
		}
		cout<<"YES\n";
		for(int i=1;i<=m;i++){
			cout<<c[i]<<' ';
		}
		cout<<'\n';
		togo:;
		for(int i=1;i<=n;i++){
			fa[i]=dep[i]=sz[i]=hes[i]=top[i]=dfn[i]=stk[i]=0;
			e[i].clear();
		}
		for(int i=1;i<=m+1;i++){
			b[i].clear();
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

11.22 2025noip模拟赛80(HZOJ NOIP2025模拟赛13)

A B C D Sum Rank
50 0 72 20 142 17/20(27/36)

A. 硬币

在一次查询中,第 \(a_i\) 种硬币可以有 \(0\sim\frac{a_{i+1}}{a_i}-1\) 个。记 \(\frac{a_{i+1}}{a_i}=b_i\),则对于所有 \(b_i\) 相同的硬币,我们希望将它们区分开来。设 \(b_i=k\) 的硬币有 \(c_k\) 个,我们将它们重新编号成 \(1\sim c_k\),第一次令编号为 \(i\) 的有 \(i\bmod k\) 个,于是我们将这 \(c_k\) 个又分成了 \(k\) 类,对每一类都进行类似的操作即可。注意因为我们不知道颜色集合,至少需要让每个硬币都有一次不是 \(0\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define fir first
#define sec second
using namespace std;
namespace asbt{
int n;
ll a[65];
map<ll,int> mp;
int main(){
	freopen("coin.in","r",stdin);
	freopen("coin.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<n;i++){
		mp[a[i+1]/a[i]]++;
	}
	int ans=1;
	for(auto t:mp){
		int cnt=0;
		ll tmp=1;
		while(tmp<=t.sec){
			tmp*=t.fir,cnt++;
		}
		ans=max(ans,cnt);
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

B. 狼群

考虑将 \(x\)\(y\) 的答案分别计算。然而并不好算,因为 \(x\)\(y\) 不能同时变化。令 \((x,y)\gets(x+y,x-y)\),于是本来的移动就变成了 \((x,y)\gets(x\pm1,y\pm1)\),两维可以独立计算。

比如在 \(x\) 维上,当前枚举要走到 \(p\),则对于第 \(i\) 只狼,一共需要向 \(p\) 的方向走 \(|x_i-p|\) 步,剩下的步数向两个方向是相同的,即会向 \(p\) 的反方向走 \(\frac{m-|x_i-p|}{2}\) 步,方案数为:

\[m\choose\frac{m-|x_i-p|}{2} \]

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=6e5+5,mod=1e9+7;
il int qpow(int x,int y=mod-2){
	int res=1;
	while(y){
		if(y&1){
			res=res*1ll*x%mod;
		}
		x=x*1ll*x%mod,y>>=1;
	}
	return res;
}
il int pls(int x,int y){
	return x+y<mod?x+y:x+y-mod;
}
il void add(int &x,int y){
	x=pls(x,y);
}
il int mns(int x,int y){
	return x<y?x-y+mod:x-y;
}
il void sub(int &x,int y){
	x=mns(x,y);
}
int n,m,xx[maxn],yy[maxn];
int fac[maxn],inv[maxn];
il void init(int n=6e5){
	fac[0]=1;
	for(int i=1;i<=n;i++){
		fac[i]=fac[i-1]*1ll*i%mod;
	}
	inv[n]=qpow(fac[n]);
	for(int i=n;i;i--){
		inv[i-1]=inv[i]*1ll*i%mod;
	}
}
il int C(int x,int y){
	return x<y||y<0?0:fac[x]*1ll*inv[y]%mod*inv[x-y]%mod;
}
int main(){
	freopen("wolf.in","r",stdin);
	freopen("wolf.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	init();
	cin>>n>>m;
	for(int i=1,x,y;i<=n;i++){
		cin>>x>>y;
		xx[i]=x+y,yy[i]=x-y;
	}
	int ans1=0,ans2=0;
	for(int i=-3e5;i<=3e5;i++){
		int res1=1,res2=1;
		for(int j=1;j<=n;j++){
			if((m-abs(xx[j]-i))&1){
				res1=0;
			}else{
				res1=res1*1ll*C(m,(m-abs(xx[j]-i))>>1)%mod;
			}
			if((m-abs(yy[j]-i))&1){
				res2=0;
			}else{
				res2=res2*1ll*C(m,(m-abs(yy[j]-i))>>1)%mod;
			}
		}
		add(ans1,res1),add(ans2,res2);
	}
	cout<<ans1*1ll*ans2%mod;
	return 0;
}
}
int main(){return asbt::main();}

C. 比赛

考虑建图,每个人向所有他能战胜的人连边,于是当且仅当 \(x\) 所在强连通分量的入度为 \(0\) 时可以赢得比赛(可以证明只可能有一个强连通分量入度为 \(0\))。

考虑强连通分量在这三个序列上的位置,实际上就是一些纵向的划分。给三个数组间相同的数连边,划分的位置就是没有边经过的位置。证明是显然的。

image

于是我们用线段树维护一下空隙的位置即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define lid id<<1
#define rid id<<1|1
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,m,a[3][maxn],b[3][maxn],tag[maxn<<2];
pii tr[maxn<<2];
il void pushup(int id){
	tr[id]=min(tr[lid],tr[rid]);
}
il void pushtag(int id,int x){
	tag[id]+=x,tr[id].fir+=x;
}
il void pushdown(int id){
	if(tag[id]){
		pushtag(lid,tag[id]);
		pushtag(rid,tag[id]);
		tag[id]=0;
	}
}
il void build(int id,int l,int r){
	if(l==r){
		tr[id]=mp(0,l);
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pushup(id);
}
il void add(int id,int L,int R,int l,int r,int x){
	if(l>r){
		return ;
	}
	if(L>=l&&R<=r){
		pushtag(id,x);
		return ;
	}
	pushdown(id);
	int mid=(L+R)>>1;
	if(l<=mid){
		add(lid,L,mid,l,r,x);
	}
	if(r>mid){
		add(rid,mid+1,R,l,r,x);
	}
	pushup(id);
}
int main(){
	freopen("match.in","r",stdin);
	freopen("match.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i:{0,1,2}){
		for(int j=1;j<=n;j++){
			cin>>a[i][j];
			b[i][a[i][j]]=j;
		}
	}
	build(1,1,n);
	for(int i=1;i<=n;i++){
		add(1,1,n,min(b[0][i],b[1][i]),max(b[0][i],b[1][i])-1,1);
		add(1,1,n,min(b[1][i],b[2][i]),max(b[1][i],b[2][i])-1,1);
	}
	while(m--){
		int opt;
		cin>>opt;
		if(opt==1){
			int x;
			cin>>x;
			cout<<(b[0][x]<=tr[1].sec?"Yes":"No")<<'\n';
		}else{
			int p,x,y;
			cin>>p>>x>>y;
			switch(--p){
				case 0:{
					add(1,1,n,min(b[0][x],b[1][x]),max(b[0][x],b[1][x])-1,-1);
					add(1,1,n,min(b[0][y],b[1][y]),max(b[0][y],b[1][y])-1,-1);
					break;
				}case 1:{
					add(1,1,n,min(b[0][x],b[1][x]),max(b[0][x],b[1][x])-1,-1);
					add(1,1,n,min(b[0][y],b[1][y]),max(b[0][y],b[1][y])-1,-1);
					add(1,1,n,min(b[1][x],b[2][x]),max(b[1][x],b[2][x])-1,-1);
					add(1,1,n,min(b[1][y],b[2][y]),max(b[1][y],b[2][y])-1,-1);
					break;
				}default:{
					add(1,1,n,min(b[1][x],b[2][x]),max(b[1][x],b[2][x])-1,-1);
					add(1,1,n,min(b[1][y],b[2][y]),max(b[1][y],b[2][y])-1,-1);
					break;
				}
			}
			swap(a[p][b[p][x]],a[p][b[p][y]]),swap(b[p][x],b[p][y]);
			switch(p){
				case 0:{
					add(1,1,n,min(b[0][x],b[1][x]),max(b[0][x],b[1][x])-1,1);
					add(1,1,n,min(b[0][y],b[1][y]),max(b[0][y],b[1][y])-1,1);
					break;
				}case 1:{
					add(1,1,n,min(b[0][x],b[1][x]),max(b[0][x],b[1][x])-1,1);
					add(1,1,n,min(b[0][y],b[1][y]),max(b[0][y],b[1][y])-1,1);
					add(1,1,n,min(b[1][x],b[2][x]),max(b[1][x],b[2][x])-1,1);
					add(1,1,n,min(b[1][y],b[2][y]),max(b[1][y],b[2][y])-1,1);
					break;
				}default:{
					add(1,1,n,min(b[1][x],b[2][x]),max(b[1][x],b[2][x])-1,1);
					add(1,1,n,min(b[1][y],b[2][y]),max(b[1][y],b[2][y])-1,1);
					break;
				}
			}
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

D. 半完全幂

以下除法和开根都下取整。

首先差分,求 \([1,n]\) 中满足 \(a\times b^c\) 的数。只需要考虑 \(c=2,3\) 即可,因为对于 \(c=2k\ (k>1)\) 的情况可以化为 \(a\times(b^k)^2\),对于 \(c=2k+1\ (k>1)\) 的情况可以化为 \((ab)\times(b^k)^2\)

\(f_k(n)\) 表示 \(n\) 不含 \(\ge k\) 次方的因子。

对于 \(c=2\),直接枚举 \(a\) 即可计算 \(b\) 的数量:

\[\sum_{a=1}^{\sqrt[3]{n}}f_2(a)\left(\sqrt{\frac{n}{a}}-a\right) \]

此时 \(c=3\) 就要麻烦一些,因为可能和 \(c=2\) 的情况算重。考虑什么时候 \(a\times b^3=a'\times b'^3\)。对于 \(ab\) 最大的平方因子 \(k^2\),有 \(a\times b^3=\frac{ab}{k^2}\times(bk)^2\),故算重当且仅当 \(\frac{ab}{k^2}<bk\),即 \(k>\sqrt[3]{a}\)

于是对于 \(c=3\),答案为:

\[\begin{aligned} & \sum_{a=1}^{\sqrt[4]{n}} f_{3}(a) \sum_{b=a+1}^{\sqrt[3]{\frac{n}{a}}} \sum_{k=1}^{\sqrt[3]{a}}\left[k^{2}\left|\right.a b\right] \times f_{2}\left(\frac{a b}{k^{2}}\right) \\ = & \sum_{a=1}^{\sqrt[4]{n}} f_{3}(a) \sum_{k=1}^{\sqrt[3]{a}} \sum_{b=a+1}^{\sqrt[3]{\frac{n}{a}}}\left[k^{2}\left|\right.a b\right] \times f_{2}\left(\frac{a b}{k^{2}}\right) \end{aligned} \]

解释一下,就是说仅当 \(k^2\)\(ab\) 的最大平方因子时 \(\left[k^{2}\left|\right.a b\right] \times f_{2}\left(\frac{a b}{k^{2}}\right)\) 成立。

枚举 \(a\)\(k\)\(O(\sqrt[3]{n})\) 的,可以接受。记 \(g=\gcd(a,k^2),a'=\frac{a}{g},k'=\frac{k^2}{g},b'=\frac{b}{k'}\),有:

\[\begin{aligned} &\sum_{a=1}^{\sqrt[4]{n}} f_{3}(a) \sum_{k=1}^{\sqrt[3]{a}} \sum_{b^{\prime}=\frac{a}{k^{\prime}}+1}^{\frac{\sqrt[3]{\frac{n}{a}}}{k^{\prime}}} f_{2}\left(a^{\prime} b^{\prime}\right)\\ =&\sum_{a=1}^{\sqrt[4]{n}} f_{3}(a) \sum_{k=1}^{\sqrt[3]{a}} \sum_{b^{\prime}=\frac{a}{k^{\prime}}+1}^{\frac{\sqrt[3]{\frac{n}{a}}}{k^{\prime}}}f_2(a')f_2(b')[\gcd(a',b')=1]\\ =&\sum_{a=1}^{\sqrt[4]{n}} f_{3}(a) \sum_{k=1}^{\sqrt[3]{a}} f_{2}\left(a^{\prime}\right) \sum_{b^{\prime}=\frac{a}{k^{\prime}}+1}^{\frac{\sqrt[3]{\frac{n}{a}}}{k^{\prime}}} f_{2}\left(b^{\prime}\right)\left[\operatorname{gcd}\left(a^{\prime}, b^{\prime}\right)=1\right] \\ =&\sum_{a=1}^{\sqrt[4]{n}} f_{3}(a) \sum_{k=1}^{\sqrt[3]{a}} f_{2}\left(a^{\prime}\right) \sum_{b^{\prime}=\frac{a}{k^{\prime}}+1}^{\frac{\sqrt[3]{\frac{n}{a}}}{k^{\prime}}} f_{2}\left(b^{\prime}\right)\sum_{d|\gcd(a',b')}\mu(d)\\ = & \sum_{a=1}^{\sqrt[4]{n}} f_{3}(a) \sum_{k=1}^{\sqrt[3]{a}} f_{2}\left(a^{\prime}\right) \sum_{d \mid a^{\prime}} \mu(d) \sum_{b'^{\prime}=\frac{a}{k^{\prime} d}+1}^{\frac{\sqrt[3]{\frac{n}{a}}}{k^{\prime} d}} f_{2}\left(b'^{\prime} d\right) \\ = & \sum_{a=1}^{\sqrt[4]{n}} f_{3}(a) \sum_{k=1}^{\sqrt[3]{a}} f_{2}\left(a^{\prime}\right) \sum_{d \mid a^{\prime}} \mu(d)\left(S_{d}\left(\frac{\sqrt[3]{\frac{n}{a}}}{k^{\prime} d}\right)-S_{d}\left(\frac{a}{k^{\prime} d}\right)\right) \end{aligned} \]

其中 \(S_k(n)=\sum_{i=1}^{n}f_2(ki)\)。时间复杂度 \(O(\sqrt[3]{n}\log n)\)

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
int prm[600005],prn,mpf[600005],mu[600005];
bool npr[600005];
vector<int> fc[20005],s[20005];
il void euler(int n=6e5){
	mu[1]=1;
	for(int i=2;i<=n;i++){
		if(!npr[i]){
			prm[++prn]=i,mpf[i]=i,mu[i]=-1;
		}
		for(int j=1;j<=prn&&i*prm[j]<=n;j++){
			npr[i*prm[j]]=1,mpf[i*prm[j]]=prm[j];
			if(i%prm[j]==0){
				mu[i*prm[j]]=0;
				break;
			}
			mu[i*prm[j]]=-mu[i];
		}
	}
}
il int f(int n,int k){
	int lst=0,cnt=0;
	while(n>1){
		if(mpf[n]==lst){
			if(++cnt==k){
				return 0;
			}
		}else{
			lst=mpf[n],cnt=1;
		}
		n/=mpf[n];
	}
	return 1;
}
il void init(int n=2e4,int m=8e16){
	euler();
	for(int i=1;i<=n;i++){
		s[i].pb(0);
		for(int j=1;i*j*i*j*i*j<=m;j++){
			s[i].pb(s[i][j-1]+f(i*j,2));
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j+=i){
			fc[j].pb(i);
		}
	}
}
il int solve(int n){
	int ans=0;
	for(int i=1;i*i*i<=n;i++){
		ans+=f(i,2)*((int)sqrtl(n/i)-i);
	}
	for(int a=1;a*a*a*a<=n;a++){
		if(!f(a,3)){
			continue;
		}
		int t=cbrtl(n/a);
		if(t<a){
			continue;
		}
		for(int k=1;k*k*k<=a;k++){
			int g=__gcd(a,k*k);
			int aa=a/g,kk=k*k/g;
			if(!f(aa,2)){
				continue;
			}
			for(int d:fc[aa]){
				ans+=mu[d]*(s[d][t/kk/d]-s[d][a/kk/d]);
			}
		}
	}
	return ans;
}
int main(){
	freopen("power.in","r",stdin);
	freopen("power.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	init();
	int l,r;
	cin>>l>>r;
	cout<<solve(r)-solve(l-1);
	return 0;
}
}
signed main(){return asbt::main();}

11.24 CSP-S模拟赛加赛2

A B C D Sum Rank
50 30 60 20 160 9/15

A. 远征

注意到 \(1\le a_i,x\le1023\),因此每次最多跳 \(10\) 次,于是我们给每个位置维护 \(nxt_{i,x}\) 表示 \(i\) 后面第一个 \(a_j\subseteq x\) 的位置 \(j\) 即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e4+5;
int n,m,a[maxn],b[maxn],nxt[maxn][1024];
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
	}
	for(int i=0;i<=1023;i++){
		nxt[n+1][i]=n+1;
	}
	for(int i=n;i;i--){
		for(int j=0;j<=1023;j++){
			if((j|a[i])==j){
				nxt[i][j]=i;
			}else{
				nxt[i][j]=nxt[i+1][j];
			}
		}
	}
	cin>>m;
	ll ans=0;
	while(m--){
		int l,r,x;
		cin>>l>>r>>x;
		ll res=0;
		while(nxt[l][x]<=r){
			res+=b[nxt[l][x]];
			x^=a[nxt[l][x]];
			l=nxt[l][x];
		}
		ans^=res;
	}
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}

B. 传送

显然答案只可能是 \(-1,0,1,2\) 中的一个,因为如果 \(\ge3\) 就可以连边。\(-1\)\(0\) 是容易判断的,考虑判断答案能否为 \(1\)。可以发现,答案为 \(1\) 的充要条件为存在 \(s\)\(t\) 的长为奇数的路径。考虑证明:

  • 充分性:若存在长为 \(1\) 的路径,则答案为 \(1\);若存在长为 \(2k+1\ (k\ge1)\) 的路径,则可以用一次操作将其中三条边缩成一条边,归纳到长为 \(2k-1\) 的路径。
  • 必要性:若所有路径长都为偶数,因为每次将三条边缩成一条,长度减少二,因此路径长始终为偶数,不可能为 \(1\)

于是我们只需判断 \(s\)\(t\) 是否存在长为奇数的路径。考虑它们所在连通块的另一点 \(x\),则又有充要条件:存在 两条路径 \(x\leadsto s,x\leadsto t\) 的奇偶性不同。充分性显然,必要性:如果一条 \(s\)\(t\) 的长为奇数的路径上没有 \(x\),则可以先从 \(s\) 走到 \(x\) 再原路返回 \(s\),再走到 \(t\)

于是我们对于每个连通块任选一个点跑一边 bfs 求出到每个点是否存在长为奇数/偶数的路径即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace asbt{
const int maxn=1e6+5;
int T,n,m,q,fa[maxn],f[maxn][2];
vector<int> e[maxn];
il int find(int x){
	return x!=fa[x]?fa[x]=find(fa[x]):x;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T>>n>>m;
	for(int i=1;i<=n;i++){
		fa[i]=i;
	}
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		e[u].pb(v),e[v].pb(u);
		fa[find(u)]=find(v);
	}
	for(int i=1;i<=n;i++){
		if(!f[i][0]&&!f[i][1]){
			queue<pii> q;
			f[i][0]=1,q.push(mp(i,0));
			while(q.size()){
				int u=q.front().fir,d=q.front().sec;
				q.pop();
				for(int v:e[u]){
					if(!f[v][d^1]){
						f[v][d^1]=1;
						q.push(mp(v,d^1));
					}
				}
			}
		}
	}
	cin>>q;
	while(q--){
		int u,v;
		cin>>u>>v;
		if(u==v){
			cout<<0<<'\n';
		}else if(find(u)!=find(v)){
			cout<<-1<<'\n';
		}else if(f[u][0]&&f[v][1]||f[u][1]&&f[v][0]){
			cout<<1<<'\n';
		}else{
			cout<<2<<'\n';
		}
	}
	return 0;
}
}
signed main(){return asbt::main();}

C. 先辈

对于形如 \(aabcab\) 的子序列,我们考虑在 \(c\) 处统计答案。设:

  • \(f_{0,i,a}\) 表示 \([1,i]\) 形如 \(a\) 的子序列个数
  • \(f_{1,i,a}\) 表示 \([1,i]\) 形如 \(aa\) 的子序列个数
  • \(f_{2,i,a,b}\) 表示 \([1,i]\) 形如 \(aab\) 的子序列个数
  • \(f_{3,i,a,b}\) 表示 \([i,n]\) 形如 \(ab\) 的子序列个数

转移是显然的,时空都爆。贡献实际上来自 \(f_2\)\(f_3\)\(i\) 变化时它们都只会变化 \(26\) 个位置,考虑滚动数组,动态维护这两个数组和它们对答案的贡献。时间复杂度 \(O(26n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e5+5,mod=114514;
il int pls(int x,int y){
	return x+y<mod?x+y:x+y-mod;
}
il void add(int &x,int y){
	x=pls(x,y);
}
il int mns(int x,int y){
	return x<y?x-y+mod:x-y;
}
il void sub(int &x,int y){
	x=mns(x,y);
}
int n,f[2][maxn][26],g[2][26][26];
string s;
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>s;
	n=s.size(),s=" "+s;
	for(int i=1;i<=n;i++){
		s[i]-='a';
		for(int j=0;j<=25;j++){
			f[0][i][j]=pls(f[0][i-1][j],s[i]==j);
			f[1][i][j]=pls(f[1][i-1][j],s[i]==j?f[0][i-1][j]:0);
			add(g[0][j][s[i]],j==s[i]?0:f[1][i-1][j]);
		}
	}
//	for(int j=0;j<=25;j++){
//		for(int k=0;k<=25;k++){
//			cout<<g[0][j][k]<<' ';
//		}
//		cout<<'\n';
//	}
	int ans=0,tot=0;
	for(int i=n;i;i--){
		for(int j=0;j<=25;j++){
			sub(g[0][j][s[i]],j==s[i]?0:f[1][i-1][j]);
			sub(tot,(j==s[i]?0:f[1][i-1][j])*1ll*g[1][j][s[i]]%mod);
			if(i<n){
				add(g[1][s[i+1]][j],j==s[i+1]?0:mns(f[0][n][j],f[0][i+1][j]));
				tot=(tot+g[0][s[i+1]][j]*1ll*(j==s[i+1]?0:mns(f[0][n][j],f[0][i+1][j])))%mod;
			}
		}
		for(int j=0;j<=25;j++){
			sub(tot,g[0][j][s[i]]*1ll*g[1][j][s[i]]%mod);
			sub(tot,g[0][s[i]][j]*1ll*g[1][s[i]][j]%mod);
		}
		add(ans,tot);
		for(int j=0;j<=25;j++){
			tot=(tot+g[0][j][s[i]]*1ll*g[1][j][s[i]])%mod;
			tot=(tot+g[0][s[i]][j]*1ll*g[1][s[i]][j])%mod;
		}
	}
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}

D. 矩阵

\(A\) 看作邻接矩阵,则要求为该图中没有长为 \(k\) 的链且没有环(因为 \(A^k_{i,j}\) 表示 \(i\)\(j\) 的长为 \(k\) 的路径数)。考虑均值不等式,我们将 \(n\) 个点平均分成 \(k\) 份,份内不连边,每一份向后面的每一份连边。答案为(以下记 \(\%\) 为取模运算,除法向下取整):

\[\begin{aligned} &\sum_{i=1}^{n\%m}\left(\frac{n}{m}+1\right)\left[n-\sum_{j=1}^{i}\left(\frac{n}{m}+1\right)\right]+\sum_{i=n\%m+1}^{m}\frac{n}{m}\left[n-\sum_{j=1}^{n\%m}\left(\frac{n}{m}+1\right)-\sum_{j=n\%m+1}^{i}\frac{n}{m}\right]\\ =&\sum_{i=1}^{n\%m}\left(\frac{n}{m}+1\right)\left[n-i\left(\frac{n}{m}+1\right)\right]+\sum_{i=n\%m+1}^{m}\frac{n}{m}\left[n-n\%m\left(\frac{n}{m}+1\right)-\left(i-n\%m\right)\frac{n}{m}\right]\\ =&n\%m\left(\frac{n}{m}+1\right)n+\left(m-n\%m\right)\frac{n}{m}\left(n-n\%m\right)-\left(\frac{n}{m}+1\right)^2\sum_{i=1}^{n\%m}i-\left(\frac{n}{m}\right)^2\sum_{i=n\%m+1}^{m}i \end{aligned} \]

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
ll n,m;
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	cout<<n*(n/m+1)*(n%m)+(m-n%m)*(n/m)*(n-n%m)-(n/m+1)*(n/m+1)*((n%m+1)*(n%m)/2)-(n/m)*(n/m)*((m+n%m+1)*(m-n%m)/2);
	return 0;
}
}
signed main(){return asbt::main();}

11.25 2025noip模拟赛83

A B C D Sum Rank
100 - - 100 200 2/15

A. 弹珠游戏

首先考虑最小的总不满意度。因为要对每个人求和,我们只需要考虑每个弹珠是它那一组的第几个。我们希望让第一个的位置尽量靠右,第三个的位置尽量靠左,因此每次贪心地取每个颜色最靠左的位置即可。

然后考虑计算方案数。首先可以发现一个性质,如果一个弹珠是这一组的第二个,则可以通过它确定这一组的第一个的颜色。这是因为如果这一组的第一个可以有两种颜色,则这三个弹珠可以直接成组,这个弹珠就成第三个了。那么依次考虑每个弹珠,记录在这之前有多少个人个弹珠状态为 \(R,G,B,RG,RB,GB\),对每个弹珠的颜色和在组中的位置分讨计算答案即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=3e5+5,mod=998244353;
il int pls(int x,int y){
	return x+y<mod?x+y:x+y-mod;
}
il void add(int &x,int y){
	x=pls(x,y);
}
il int mns(int x,int y){
	return x<y?x-y+mod:x-y;
}
il void sub(int &x,int y){
	x=mns(x,y);
}
int n,a[maxn],pre[maxn],dp[maxn];
string s;
priority_queue<int> rp,gp,bp;
int main(){
	freopen("A.in","r",stdin);
	freopen("A.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>s;
	s=" "+s;
	for(int i=1;i<=n*3;i++){
		switch(s[i]){
			case 'R':{
				rp.push(i);
				break;
			}case 'G':{
				gp.push(i);
				break;
			}default:{
				bp.push(i);
				break;
			}
		}
	}
	for(int i=1;i<=n;i++){
		int p[3]={rp.top(),gp.top(),bp.top()};
		sort(p,p+3),rp.pop(),gp.pop(),bp.pop();
		a[p[0]]=-1,a[p[1]]=0,a[p[2]]=1;
		pre[p[1]]=p[0];
	}
//	for(int i=1;i<=n*3;i++){
//		cout<<a[i]<<' ';
//	}
//	cout<<'\n';
	dp[0]=1;
	for(int i=1,r=0,g=0,b=0,rg=0,rb=0,gb=0;i<=n*3;i++){
		switch(s[i]){
			case 'R':{
				switch(a[i]){
					case -1:{
						dp[i]=dp[i-1],r++;
						break;
					}case 0:{
						switch(s[pre[i]]){
							case 'G':{
								dp[i]=dp[i-1]*1ll*g%mod,g--,rg++;
								break;
							}default:{
								dp[i]=dp[i-1]*1ll*b%mod,b--,rb++;
								break;
							}
						}
						break;
					}default:{
						dp[i]=dp[i-1]*1ll*gb%mod,gb--;
						break;
					}
				}
				break;
			}case 'G':{
				switch(a[i]){
					case -1:{
						dp[i]=dp[i-1],g++;
						break;
					}case 0:{
						switch(s[pre[i]]){
							case 'R':{
								dp[i]=dp[i-1]*1ll*r%mod,r--,rg++;
								break;
							}default:{
								dp[i]=dp[i-1]*1ll*b%mod,b--,gb++;
								break;
							}
						}
						break;
					}default:{
						dp[i]=dp[i-1]*1ll*rb%mod,rb--;
						break;
					}
				}
				break;
			}default:{
				switch(a[i]){
					case -1:{
						dp[i]=dp[i-1],b++;
						break;
					}case 0:{
						switch(s[pre[i]]){
							case 'R':{
								dp[i]=dp[i-1]*1ll*r%mod,r--,rb++;
								break;
							}default:{
								dp[i]=dp[i-1]*1ll*g%mod,g--,gb++;
								break;
							}
						}
						break;
					}default:{
						dp[i]=dp[i-1]*1ll*rg%mod,rg--;
						break;
					}
				}
				break;
			}
		}
	}
	int ans=dp[n*3];
	for(int i=1;i<=n;i++){
		ans=ans*1ll*i%mod;
	}
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}

B. 晚会

题目的限制相当于对于任意三元环边权形如 \((a,a,b)\ (a\le b)\)。考虑最小化每条边的权值,因为边权大的边对其他边限制更少,所以考虑求最大生成树。当加入一条树边 \((u,v,w)\) 时,它连接的两个连通块之间的所有连边都会被限制成 \(w\)。考虑无解情况,对于非树边 \((u,v,w)\),若树上 \(u\leadsto v\) 的路径上的最小值不为 \(w\) 则不合法。图有可能不连通,剩下的边全设为 \(1\) 即可。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace asbt{
const int maxn=3e5+5,inf=1e9;
int n,m,fa[maxn],sz[maxn],dep[maxn];
int anc[maxn][20],mn[maxn][20];
bool vis[maxn];
vector<pii> e[maxn];
struct edge{
	int u,v,w;
	il bool operator<(const edge &x)const{
		return w>x.w;
	}
}a[maxn];
il int find(int x){
	return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void dfs(int u,int fa){
	dep[u]=dep[fa]+1;
	anc[u][0]=fa;
	for(int i=1;i<=18;i++){
		anc[u][i]=anc[anc[u][i-1]][i-1];
		mn[u][i]=min(mn[u][i-1],mn[anc[u][i-1]][i-1]);
	}
	for(pii i:e[u]){
		int v=i.fir,w=i.sec;
		if(v==fa){
			continue;
		}
		mn[v][0]=w;
		dfs(v,u);
	}
}
il int query(int u,int v){
	if(dep[u]<dep[v]){
		u^=v^=u^=v;
	}
	int res=inf,ddp=dep[u]-dep[v],t=0;
	while(ddp){
		if(ddp&1){
			res=min(res,mn[u][t]);
			u=anc[u][t];
		}
		ddp>>=1,t++;
	}
	if(u==v){
		return res;
	}
	for(int i=18;~i;i--){
		if(anc[u][i]!=anc[v][i]){
			res=min({res,mn[u][i],mn[v][i]});
			u=anc[u][i],v=anc[v][i];
		}
	}
	return min({res,mn[u][0],mn[v][0]});
}
int main(){
	freopen("B.in","r",stdin);
	freopen("B.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>a[i].u>>a[i].v>>a[i].w;
	}
	int ans=0,cnt=0;
	for(int i=1;i<=n;i++){
		fa[i]=i,sz[i]=1;
	}
	sort(a+1,a+m+1);
	for(int i=1;i<=m;i++){
		int u=find(a[i].u),v=find(a[i].v);
		if(u!=v){
			cnt+=sz[u]*sz[v];
			ans+=a[i].w*sz[u]*sz[v];
			fa[u]=v,sz[v]+=sz[u],vis[i]=1;
			e[u].pb(mp(v,a[i].w));
			e[v].pb(mp(u,a[i].w));
		}
	}
	for(int i=1;i<=n;i++){
		if(!dep[i]){
			dfs(i,0);
		}
	}
	for(int i=1;i<=m;i++){
		if(vis[i]){
			continue;
		}
		if(query(a[i].u,a[i].v)!=a[i].w){
			cout<<-1;
			return 0;
		}
	}
	cout<<ans+n*(n-1)/2-cnt;
	return 0;
}
}
signed main(){return asbt::main();}

C. 优美的字符串

首先考虑没有 \(?\) 怎么做。对于一段前缀 \(1\sim i\)(其中 \(i\) 为奇数),任意时刻有两种状态:

  • 被删成了一个点

  • 被删成了若干个点

于是可以考虑 DP。设 \(f(i,j=0/1,k=0/1)\) 表示考虑到 \(i\),结尾为 \(j\)\(1\sim i\) 能否删成 \(k\)。于是有转移:

  • 先将 \(1\sim i-2\) 删成 \(x\),再删除 \(i\)\(f(i,j,F(x,s_{i-1},j))\gets f(i-2,s_{i-2},x)\)
  • 还没将 \(1\sim i-2\) 删完时删除 \(i\),然后再接着删 \(1\sim i-2\)\(f(i,j,k)\gets f(i-2,F(s_{i-2},s_{i-1,j}),k)\)

于是可以写出以下的代码:

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int T,n,F[8],f[maxn][2][2];
char s[maxn];
il int calc(int x,int y,int z){
	return F[x<<2|y<<1|z];
}
il void solve(){
	for(int i:{0b000,0b100,0b010,0b110,0b001,0b101,0b011,0b111}){
		scanf("%1d",F+i);
	}
	scanf(" %s",s+1);
	n=strlen(s+1);
	for(int i=1;i<=n;i++){
		s[i]^=48;
		f[i][0][0]=f[i][0][1]=f[i][1][0]=f[i][1][1]=0;
	}
	f[1][0][0]=f[1][1][1]=1;
	for(int i=3;i<=n;i+=2){
		for(int x:{0,1}){
			f[i][x][0]|=f[i-2][s[i-2]][0]&&calc(0,s[i-1],x)==0;
			f[i][x][0]|=f[i-2][s[i-2]][1]&&calc(1,s[i-1],x)==0;
			f[i][x][1]|=f[i-2][s[i-2]][0]&&calc(0,s[i-1],x)==1;
			f[i][x][1]|=f[i-2][s[i-2]][1]&&calc(1,s[i-1],x)==1;
			f[i][x][0]|=f[i-2][calc(s[i-2],s[i-1],x)][0];
			f[i][x][1]|=f[i-2][calc(s[i-2],s[i-1],x)][1];
		}
	}
	cout<<f[n][s[n]][1]<<'\n';
}
int main(){
	freopen("C.in","r",stdin);
	freopen("C.out","w",stdout);
	scanf("%d",&T);
	while(T--){
		solve();
	}
	return 0;
}
}
signed main(){return asbt::main();}

然后考虑加上 \(?\) 后计数,考虑 DP 套 DP,设 \(g(i,0/1,0/1,0/1,0/1)\) 表示 \(f(i,0,0),f(i,0,1),f(i,1,0),f(i,1,1)\) 分别是什么的方案数。此时枚举 \(s_{i-2},s_{i-1},s_i\) 即可转移。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define get(x,y) ((x)<<1|(y))
#define calc(x,y,z) F[(x)<<2|(y)<<1|(z)]
using namespace std;
namespace asbt{
const int maxn=1e5+5,mod=998244353;
il int pls(int x,int y){
	return x+y<mod?x+y:x+y-mod;
}
il void add(int &x,int y){
	x=pls(x,y);
}
il int mns(int x,int y){
	return x<y?x-y+mod:x-y;
}
il void sub(int &x,int y){
	x=mns(x,y);
}
int T,n,F[8],f[maxn][16];
char s[maxn];
il void solve(){
	for(int i:{0b000,0b100,0b010,0b110,0b001,0b101,0b011,0b111}){
		scanf("%1d",F+i);
	}
	scanf(" %s",s+1);
	n=strlen(s+1);
	for(int i=1;i<=n;i++){
		for(int j=0;j<=15;j++){
			f[i][j]=0;
		}
	}
	f[1][0b1001]=1;
	for(int i=3;i<=n;i+=2){
		for(int S=0;S<=15;S++){
			for(int y=(s[i-1]=='1'?1:0);y<=(s[i-1]=='0'?0:1);y++){
				for(int z=(s[i-2]=='1'?1:0);z<=(s[i-2]=='0'?0:1);z++){
					int T=0;
					for(int x:{0,1}){
						T|=(((S>>get(z,0))&1)&(!calc(0,y,x)))<<get(x,0);
						T|=(((S>>get(z,1))&1)&(!calc(1,y,x)))<<get(x,0);
						T|=(((S>>get(z,0))&1)&calc(0,y,x))<<get(x,1);
						T|=(((S>>get(z,1))&1)&calc(1,y,x))<<get(x,1);
						T|=((S>>get(calc(z,y,x),0))&1)<<get(x,0);
						T|=((S>>get(calc(z,y,x),1))&1)<<get(x,1);
					}
					add(f[i][T],f[i-2][S]);
				}
			}
		}
	}
//	for(int i=1;i<=n;i+=2){
//		cout<<i<<":\n";
//		for(int S=0;S<=15;S++){
//			cout<<bitset<4>(S)<<' '<<f[i][S]<<'\n';
//		}
//	}
	int ans=0;
	for(int S=0;S<=15;S++){
		if(s[n]=='0'){
			if(S>>get(0,1)&1){
				add(ans,f[n][S]);
			}
		}else if(s[n]=='1'){
			if(S>>get(1,1)&1){
				add(ans,f[n][S]);
			}
		}else{
			if(S>>get(0,1)&1){
				add(ans,f[n][S]);
			}
			if(S>>get(1,1)&1){
				add(ans,f[n][S]);
			}
		}
	}
	printf("%d\n",ans);
}
int main(){
	freopen("C.in","r",stdin);
	freopen("C.out","w",stdout);
	scanf("%d",&T);
	while(T--){
		solve();
	}
	return 0;
}
}
signed main(){return asbt::main();}
/*
1
10001100
0000001
1
1
11100111
???
6
*/

D. 选举

显然可以莫队,容易想到用值域分块平衡复杂度,但用分块的话删除操作就不好办了,回滚即可。

貌似普通莫队 + zkw 线段树也能卡过?

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e5+5,B=316,bnm=317,maxb=325;
int n,m,q,a[maxn],b[maxn],ans[maxn];
int st[maxb],ed[maxb],bel[maxn];
int bu[maxn],mx[maxb],mxr[maxb];
struct node{
	int l,r,vl,vr,id;
	il bool operator<(const node  &x)const{
		return r<x.r;
	}
}c[maxn];
vector<int> d[maxb];
il int query(int l,int r){
	int pl=bel[l],pr=bel[r];
	if(pl==pr){
		int res=0;
		for(int i=l;i<=r;i++){
			res=max(res,bu[i]);
		}
		return res;
	}
	int res=0;
	for(int i=pl+1;i<pr;i++){
		res=max(res,mx[i]);
	}
	for(int i=l;i<=ed[pl];i++){
		res=max(res,bu[i]);
	}
	for(int i=st[pr];i<=r;i++){
		res=max(res,bu[i]);
	}
	return res;
}
int main(){
	freopen("D.in","r",stdin);
	freopen("D.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	for(int i=1;i<=bnm;i++){
		st[i]=ed[i-1]+1;
		ed[i]=min(ed[i-1]+B,100000ll);
		for(int j=st[i];j<=ed[i];j++){
			bel[j]=i;
		}
	}
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
	}
	int cnt=0;
	for(int i=1,l,r,vl,vr;i<=q;i++){
		cin>>l>>r>>vl>>vr;
		if(bel[l]==bel[r]){
			for(int j=l;j<=r;j++){
				if(a[j]>=vl&&a[j]<=vr){
					bu[a[j]]+=b[j];
				}
				ans[i]=max(ans[i],bu[a[j]]);
			}
			for(int j=l;j<=r;j++){
				bu[a[j]]=0;
			}
		}else{
			c[++cnt]={l,r,vl,vr,i};
		}
	}
	sort(c+1,c+cnt+1);
	for(int i=1;i<=cnt;i++){
		d[bel[c[i].l]].pb(i);
	}
//	puts("666");
	for(int i=1;i<=bnm;i++){
		int l=ed[i]+1,r=ed[i];
		for(int j=1;j<=m;j++){
			bu[j]=0;
		}
		for(int j=1;j<=bnm;j++){
			mxr[j]=mx[j]=0;
		}
		for(int j:d[i]){
			while(r<c[j].r){
				r++;
				bu[a[r]]+=b[r];
				mxr[bel[a[r]]]=max(mxr[bel[a[r]]],bu[a[r]]);
			}
			for(int k=1;k<=bnm;k++){
				mx[k]=mxr[k];
			}
			while(l>c[j].l){
				l--;
				bu[a[l]]+=b[l];
				mx[bel[a[l]]]=max(mx[bel[a[l]]],bu[a[l]]);
			}
			ans[c[j].id]=query(c[j].vl,c[j].vr);
			while(l<=ed[i]){
				bu[a[l]]-=b[l];
				l++;
			}
		}
	}
	for(int i=1;i<=q;i++){
		cout<<ans[i]<<'\n';
	}
	return 0;
}
}
signed main(){return asbt::main();}

11.26 2025noip模拟赛84

A B C D Sum Rank
100 60 0 40 200 6/16

A. 豪宴

先跑若干整轮和一个散轮满足第一个条件。此时如果满足了第二个条件则直接结束;否则再跑一个整轮,观察比分的差是否变化。若不变化则不会结束,否则将在 \(O(1)\) 个整轮内结束,直接跑出来即可。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define nxt(i) ((i)==n?1:(i)+1)
using namespace std;
namespace asbt{
const int maxn=2e5+5;
int T,n,m;
string s;
il void solve(){
	cin>>n>>m>>s;
	s=" "+s;
	int cnta=0,cntb=0;
	for(int i=1;i<=n;i++){
		(s[i]=='A'?cnta:cntb)++;
	}
	int cnt=(m+max(cnta,cntb)-1)/max(cnta,cntb)-1;
	cnta*=cnt,cntb*=cnt;
	int p=1;
	for(;p<=n;p++){
		(s[p]=='A'?cnta:cntb)++;
		if(cnta>=m||cntb>=m){
			break;
		}
	}
	if(labs(cnta-cntb)>=2){
		cout<<"Yes\n"<<cnta+cntb<<'\n';
		return ;
	}
	int d=cnta-cntb;
	for(int i=nxt(p);;i=nxt(i)){
		(s[i]=='A'?cnta:cntb)++;
		if(labs(cnta-cntb)>=2){
			cout<<"Yes\n"<<cnta+cntb<<'\n';
			return ;
		}
		if(i==p){
			break;
		}
	}
	if(cnta-cntb==d){
		cout<<"No\n";
		return ;
	}
	for(int i=nxt(p);;i=nxt(i)){
		(s[i]=='A'?cnta:cntb)++;
		if(labs(cnta-cntb)>=2){
			cout<<"Yes\n"<<cnta+cntb<<'\n';
			return ;
		}
	}
}
int main(){
//	system("fc ex_dinner3.out my.out");
	freopen("dinner.in","r",stdin);
	freopen("dinner.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		solve();
	}
	return 0;
}
}
signed main(){return asbt::main();}

B. 奇巧

首先考虑如果 \(f\ge\max\{r\}\)(以下记为 \(r_{\max}\)),则将 \(r\) 倒序排列即可。那么如果 \(f<r_{\max}\),则需要先将 \(f\) 提升至 \(r_{\max}\),再跑上面的贪心。但是我们不能直接将 \(f\) 提上去,而应该在这个过程中尽可能地带上一部分区间。于是我们按 \(l\) 升序排,每次将能提升 \(f\) 的区间带上,然后再按 \(r\) 降序排走完剩下的区间即可。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int T,n,m;
bool vis[maxn];
struct node{
	int l,r,id;
}a[maxn];
int main(){
	freopen("clever.in","r",stdin);
	freopen("clever.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>m;
		int ans=0;
		for(int i=1;i<=n;i++){
			cin>>a[i].l>>a[i].r;
			a[i].id=i;
			ans+=a[i].r-a[i].l;
			vis[i]=0;
		}
		sort(a+1,a+n+1,[](node x,node y){return x.l<y.l;});
		for(int i=1;i<=n;i++){
			if(m<a[i].r){
				ans+=max(a[i].l-m,0ll);
				m=a[i].r;
				vis[a[i].id]=1;
			}
		}
		cout<<ans<<'\n';
		for(int i=1;i<=n;i++){
			if(vis[a[i].id]){
				cout<<a[i].id<<' ';
			}
		}
		sort(a+1,a+n+1,[](node x,node y){return x.r>y.r;});
		for(int i=1;i<=n;i++){
			if(!vis[a[i].id]){
				cout<<a[i].id<<' ';
			}
		}
		cout<<'\n';
	}
	return 0;
}
}
signed main(){return asbt::main();}

C. 仙缘

首先 bfs 把所有连通块跑出来。考虑上下两个连通块:

image

两个打叉的格子之间距离为 \(2\),这意味着下面的连通块停下后上面的至多 \(2\) 秒后就要停下。于是每一列相邻的两个连通块之间连一条边,跑最短路即可。点数和边数都是 \(O(nm)\) 级别的,直接用堆优化或许会 TLE(?),发现所有边长度加起来不会超过 \(nm\),于是给每个路径长度开一个 vector 即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define pb push_back
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace asbt{
//bool bg;
const int maxn=2e3+5;
const int dx[]={0,0,-1,1};
const int dy[]={-1,1,0,0};
int n,m,bel[maxn][maxn],b[maxn],dis[maxn*maxn],c[maxn*maxn],hd[maxn*maxn],enm;
bool vis[maxn*maxn];
char a[maxn][maxn],d[maxn][maxn];
pii q[maxn*maxn];
vector<int> bu[maxn*maxn];
struct{
	int v,w,nxt;
}e[maxn*maxn];
il void addedge(int u,int v,int w){
	e[++enm]={v,w,hd[u]},hd[u]=enm;
}
//bool ed;
int main(){
	freopen("fate.in","r",stdin);
	freopen("fate.out","w",stdout);
//	cout<<(&bg-&ed)/1048576.0<<'\n';
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>(a[i]+1);
	}
	int cnt=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(bel[i][j]||a[i][j]=='.'){
				continue;
			}
			int hd=1,tl=0;
			bel[i][j]=++cnt,q[++tl]=mp(i,j);
			while(hd<=tl){
				int x=q[hd].fir,y=q[hd].sec;
				hd++;
//				cout<<x<<' '<<y<<'\n';
				for(int k=0;k<=3;k++){
					if(x+dx[k]>0&&x+dx[k]<=n&&y+dy[k]>0&&y+dy[k]<=m&&a[x+dx[k]][y+dy[k]]=='#'&&!bel[x+dx[k]][y+dy[k]]){
						bel[x+dx[k]][y+dy[k]]=cnt;
						q[++tl]=mp(x+dx[k],y+dy[k]);
					}
				}
			}
		}
	}
//	return 0;
	cnt++;
	for(int i=1;i<=m;i++){
		bel[n+1][i]=cnt;
		b[i]=n+1;
	}
	for(int i=n;i;i--){
		for(int j=1;j<=m;j++){
			if(a[i][j]=='#'){
				addedge(bel[b[j]][j],bel[i][j],b[j]-i-1);
				b[j]=i;
			}
		}
	}
	memset(dis,0x3f,sizeof(dis));
	dis[cnt]=0,bu[0].pb(cnt);
	int p=0;
	while(p<=n*m){
		if(c[p]==bu[p].size()){
			p++;
			continue;
		}
		int u=bu[p][c[p]++];
		if(vis[u]){
			continue;
		}
		vis[u]=1;
		for(int i=hd[u];i;i=e[i].nxt){
			int v=e[i].v,w=e[i].w;
			if(!vis[v]&&dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				bu[dis[v]].pb(v);
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(bel[i][j]){
				d[i+dis[bel[i][j]]][j]='#';
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cout<<(d[i][j]=='#'?'#':'.');
		}
		cout<<'\n';
	}
	return 0;
}
}
signed main(){return asbt::main();}

然而洛谷上更是没保证 \(n,m\) 的数据范围,只保证了 \(nm\le10^6\),所以需要开动态数组。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define pb push_back
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace asbt{
//bool bg;
const int maxn=1e6+5;
const int dx[]={0,0,-1,1};
const int dy[]={-1,1,0,0};
int n,m,b[maxn],dis[maxn],c[maxn],hd[maxn],enm;
bool vis[maxn];
string a[maxn];
pii q[maxn];
vector<int> bu[maxn];
struct{
	int v,w,nxt;
}e[maxn];
il void addedge(int u,int v,int w){
	e[++enm]={v,w,hd[u]},hd[u]=enm;
}
//bool ed;
int main(){
//	cout<<(&bg-&ed)/1048576.0<<'\n';
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	vector<vector<int>> bel(n+5,vector<int>(m+5,0));
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[i]=" "+a[i];
	}
	int cnt=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(bel[i][j]||a[i][j]=='.'){
				continue;
			}
			int hd=1,tl=0;
			bel[i][j]=++cnt,q[++tl]=mp(i,j);
			while(hd<=tl){
				int x=q[hd].fir,y=q[hd].sec;
				hd++;
//				cout<<x<<' '<<y<<'\n';
				for(int k=0;k<=3;k++){
					if(x+dx[k]>0&&x+dx[k]<=n&&y+dy[k]>0&&y+dy[k]<=m&&a[x+dx[k]][y+dy[k]]=='#'&&!bel[x+dx[k]][y+dy[k]]){
						bel[x+dx[k]][y+dy[k]]=cnt;
						q[++tl]=mp(x+dx[k],y+dy[k]);
					}
				}
			}
		}
	}
//	return 0;
	cnt++;
	for(int i=1;i<=m;i++){
		bel[n+1][i]=cnt;
		b[i]=n+1;
	}
	for(int i=n;i;i--){
		for(int j=1;j<=m;j++){
			if(a[i][j]=='#'){
				addedge(bel[b[j]][j],bel[i][j],b[j]-i-1);
				b[j]=i;
			}
		}
	}
	memset(dis,0x3f,sizeof(dis));
	dis[cnt]=0,bu[0].pb(cnt);
	int p=0;
	while(p<=n*m){
		if(c[p]==bu[p].size()){
			p++;
			continue;
		}
		int u=bu[p][c[p]++];
		if(vis[u]){
			continue;
		}
		vis[u]=1;
		for(int i=hd[u];i;i=e[i].nxt){
			int v=e[i].v,w=e[i].w;
			if(!vis[v]&&dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				bu[dis[v]].pb(v);
			}
		}
	}
	vector<vector<int>> d(n+5,vector<int>(m+5,0));
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(bel[i][j]){
				d[i+dis[bel[i][j]]][j]=1;
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cout<<(d[i][j]?'#':'.');
		}
		cout<<'\n';
	}
	return 0;
}
}
signed main(){return asbt::main();}

D. 离魂

考虑枚举矩形最上面一行 \(i\),对于每一列的第一个 \(1\) 画出一条轮廓表示此时合法的矩形所在范围:

image

然后我们就能得到每一列最大的高度 \(d_j\)。此时对于第 \(j\) 列,我们求出其左侧第一个小于等于 \(d_j\) 的位置 \(ll_j\) 和右侧第一个小于 \(d_j\) 的位置 \(rr_j\),钦定 \((i,j)\) 在矩形内计算方案,就可以做到不重不漏。

考虑怎么计算方案,设 \(g(n,m)\) 表示 \(n\times m\) 的网格中钦定第一行有多少 \(\ge k\) 的矩形,于是答案为:

\[g(d_j,rr_j-ll_j-1)-g(d_j,j-ll_j-1)-g(d_j,rr_j-j-1) \]

\(g\) 不好求,再设 \(f(n,m)\) 表示 \(n\times m\) 的网格中有多少 \(\ge k\) 的矩形,于是有 \(g(n,m)=f(n,m)-f(n-1,m)\)。考虑计算 \(f\)

\[\begin{aligned} f(n,m)&=\sum_{i=1}^{n}\sum_{j=1}^{m}[i\times j\ge k](n-i+1)(m-j+1)\\ &=\sum_{i=1}^{n}(n-i+1)\sum_{j=1}^{m}\left[j\ge\left\lceil\frac{k}{i}\right\rceil\right](m-j+1) \end{aligned} \]

于是再设 \(h(n,m)=\sum_{j=1}^{m}\left[j\ge\left\lceil\frac{k}{i}\right\rceil\right](m-j+1)\) 即可预处理。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e3+5;
int n,m,k,a[maxn][maxn],b[maxn],c[maxn],d[maxn];
int s[maxn],ll[maxn],rr[maxn];
int f[maxn][maxn],g[maxn][maxn];
il void __init_f_and_g_hahahaha(int n=2e3){
	for(int j=1;j<=n;j++){
		for(int i=j;i;i--){
			g[i][j]=g[i+1][j]+j-i+1;
		}
	}
	for(int j=1;j<=n;j++){
		int t1=0,t2=0;
		for(int i=1;i<=n;i++){
			t1+=(k+i-1)/i<=n?g[(k+i-1)/i][j]:0;
			t2+=i*((k+i-1)/i<=n?g[(k+i-1)/i][j]:0);
			f[i][j]=(i+1)*t1-t2;
		}
	}
}
il void __get_ll_and_rr_hahahaha(){
	int top=0;
	s[0]=0;
	for(int i=1;i<=m;i++){
		while(top&&d[i]<d[s[top]]){
			top--;
		}
		ll[i]=s[top]+1;
		s[++top]=i;
	}
	top=0,s[0]=m+1;
	for(int i=m;i;i--){
		while(top&&d[i]<=d[s[top]]){
			top--;
		}
		rr[i]=s[top]-1;
		s[++top]=i;
	}
}
int main(){
	freopen("soul.in","r",stdin);
	freopen("soul.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
		}
	}
	__init_f_and_g_hahahaha();
	int ans=0;
//	return 0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			c[j]=max(c[j],i-1);
			while(c[j]<n&&a[c[j]+1][j]==0){
				c[j]++;
			}
			d[j]=c[j]-i+1;
		}
		__get_ll_and_rr_hahahaha();
		for(int j=1;j<=m;j++){
			if(!d[j]){
				continue;
			}
			ans+=f[d[j]][rr[j]-ll[j]+1]-f[d[j]-1][rr[j]-ll[j]+1];
			ans-=f[d[j]][j-ll[j]]-f[d[j]-1][j-ll[j]];
			ans-=f[d[j]][rr[j]-j]-f[d[j]-1][rr[j]-j];
		}
	}
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}

11.27 2025noip模拟赛85

A B C D Sum Rank
0 60 16 50 126 15/23

A. 宝藏(treasure)

首先可以发现,随着 \(x\) 增加,答案递减。可以类似地得到,随着中位数变小,所需的最大的 \(x\) 递增。于是我们可以用线段树和双指针求出每个中位数对应的最大的 \(x\),进一步再推出每个 \(x\) 的答案。时间复杂度线性对数。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=1e6+5;
int n,m,q,len[maxn],ans[maxn];
struct node{
	int w,t;
}a[maxn];
struct stree{
	int tr[maxn<<2],sum[maxn<<2];
	il void pushup(int id){
		tr[id]=tr[lid]+tr[rid];
		sum[id]=sum[lid]+sum[rid];
	}
	il void insert(int id,int l,int r,int p){
//		cout<<id<<'\n';
//		assert(p>=l&&p<=r);
		if(l==r){
			tr[id]++,sum[id]+=l;
			return ;
		}
		int mid=(l+r)>>1;
		if(p<=mid){
			insert(lid,l,mid,p);
		}else{
			insert(rid,mid+1,r,p);
		}
		pushup(id);
	}
	il void erase(int id,int l,int r,int p){
//		assert(p>=l&&p<=r);
		if(l==r){
			tr[id]--,sum[id]-=l;
			return ;
		}
		int mid=(l+r)>>1;
		if(p<=mid){
			erase(lid,l,mid,p);
		}else{
			erase(rid,mid+1,r,p);
		}
		pushup(id);
	}
	il int query(int,int,int,int);
}S1,S2;
il int stree::query(int id,int l,int r,int x){
//	assert(x<=S1.tr[1]&&x<=S2.tr[1]);
//		cout<<id<<' '<<l<<' '<<r<<' '<<x<<'\n';
//		fflush(stdout);
	if(tr[id]<=x){
		return sum[id];
	}
	if(l==r){
		return l*x;
	}
	int mid=(l+r)>>1;
	if(x<=tr[lid]){
		return query(lid,l,mid,x);
	}else{
		return sum[lid]+query(rid,mid+1,r,x-tr[lid]);
	}
}
int main(){
//	system("fc treasure2.out my.out");
	freopen("treasure.in","r",stdin);
//	freopen("my.out","w",stdout);
	freopen("treasure.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++){
		cin>>a[i].w>>a[i].t;
	}
	sort(a+1,a+n+1,[](node x,node y){return x.w>y.w;});
//	return 0;
	for(int i=1;i<=n;i++){
//		cout<<a[i].t<<'\n';
		S1.insert(1,0,1e6,a[i].t);
	}
//	return 0;
	for(int i=1,x=-1;i<=n;i++){
//		puts("666"),fflush(stdout);
		S1.erase(1,0,1e6,a[i].t);
//		puts("777"),fflush(stdout);
//		cout<<i<<'\n';
//		cout<<a[i].w<<":\n";
		while(1){
			x++;
//			cout<<x<<'\n';
			if(S1.tr[1]<x||S2.tr[1]<x||S1.query(1,0,1e6,x)+S2.query(1,0,1e6,x)+a[i].t>m){
				x--;
				break;
			}
//				assert(x<=S1.tr[1]&&x<=S2.tr[1]);
		}
//		puts("888"),fflush(stdout);
//		return 0;
		len[a[i].w]=x*2+1;
//		puts("999"),fflush(stdout);
		S2.insert(1,0,1e6,a[i].t);
//		puts("000"),fflush(stdout);
	}
//	return 0;
//	for(int i=0;i<=60;i++){
//		cout<<len[i]<<' ';
//	}
	memset(ans,-1,sizeof(ans));
	for(int i=1e6,p=1;~i;i--){
		while(p<=len[i]){
			ans[p]=i,p+=2;
		}
	}
	while(q--){
		int x;
		cin>>x;
		cout<<ans[x]<<'\n';
	}
	return 0;
}
}
signed main(){return asbt::main();}

B. 寻找道路(path)

除了能从 \(1\) 只走 \(0\) 走到的点外,剩下的肯定是边数越小越好,因此考虑 bfs。注意边数相同的情况需要对于每个路径权值的所有点先走 \(0\) 再走 \(1\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e6+5,mod=1e9+7;
int n,m,dis[maxn],cnt=1;
bool vis[maxn];
vector<int> e[maxn][2],vc[maxn];
il void dfs(int u){
	vis[u]=1,vc[1].pb(u);
	for(int v:e[u][0]){
		if(!vis[v]){
			dfs(v);
		}
	}
}
int main(){
	freopen("path.in","r",stdin);
//	freopen("my.out","w",stdout);
	freopen("path.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1,u,v,w;i<=m;i++){
		cin>>u>>v>>w;
		e[u][w].pb(v);
	}
	dfs(1);
	int p=0;
	while(vc[++p].size()){
//		cout<<u<<' '<<dis[u]<<":\n";
		bool flag=0;
		for(int u:vc[p]){
			for(int v:e[u][0]){
				if(!vis[v]){
					vis[v]=1;
					dis[v]=dis[u]*2ll%mod;
					if(!flag){
						flag=1,cnt++;
					}
					vc[cnt].pb(v);
	//				cout<<0<<' '<<v<<'\n';
				}
			}
		}
		flag=0;
		for(int u:vc[p]){
			for(int v:e[u][1]){
				if(!vis[v]){
					vis[v]=1;
					dis[v]=(dis[u]*2ll+1)%mod;
					if(!flag){
						flag=1,cnt++;
					}
					vc[cnt].pb(v);
	//				cout<<1<<' '<<v<<'\n';
				}
			}
		}
	}
	for(int i=2;i<=n;i++){
		cout<<(vis[i]?dis[i]:-1)<<' ';
	}
	return 0;
}
}
signed main(){return asbt::main();}

C. 猪国杀(legend)

D. 数树(count)

posted @ 2025-11-20 21:53  zhangxy__hp  阅读(47)  评论(0)    收藏  举报