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

2025CSP-S模拟赛67(HZOJ CSP-S模拟42)

A B C D Sum Rank
60(70) 25 30 5 120 5/14(7/34)

A. 乘筛积

对于单次查询,我们可以直接枚举 \(x\) 算出对应的 \(y\) 贡献答案,时间复杂度 \(O(\frac{C}{\max(p,q)})\)。根号分治即可。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=3e5+5,mod=998244353;
int n,m,kk,T,a[maxn],b[maxn],f[550][550];
il int solve(int p,int q,int *a,int *b,int n,int m){
	if(p<q){
		swap(p,q),swap(a,b),swap(n,m);
	}
	int ans=0;
	for(int i=1;i<=n&&i*p<=kk;i++){
		if((kk-i*p)%q==0){
			int j=(kk-i*p)/q;
			if(j>0&&j<=m){
				ans=(ans+a[i]*b[j])%mod;
			}
		}
	}
	return ans;
}
int main(){
	freopen("sedge.in","r",stdin);
	freopen("sedge.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>kk;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=m;i++){
		cin>>b[i];
	}
	for(int i=1;i<=547;i++){
		for(int j=1;j<=547;j++){
			f[i][j]=solve(i,j,a,b,n,m);
		}
	}
	cin>>T;
	while(T--){
		int p,q;
		cin>>p>>q;
		if(max(p,q)<=547){
			cout<<f[p][q]<<'\n';
		}else{
			cout<<solve(p,q,a,b,n,m)<<'\n';
		}
	}
	return 0;
}
}
signed main(){return asbt::main();}

B. 放进去

首先对于每个奢侈品单独考虑,不妨令 \(a_{i,p_1}\le a_{i,p_2}\le a_{i,p_3}\le\dots\le a_{i,p_m}\)。假设我们最终选的店铺集合是 \(S\),那么对于 \(i\) 我们必然选择 \(S\)\(a\) 最小(也就是最靠前)的 \(p_j\)。考虑差分,即对于所有 \(k<j\),给答案加上 \(a_{i,p_{k+1}}-a_{i,p_k}\)。考虑 SOSDP,于是我们只需要给所有 \(\{p_1,p_2,\dots,p_k\}\) 的答案加上 \(a_{i,p_{k+1}}-a_{i,p_k}\),最后再做一遍高维前缀和即可。记这个和为 \(f_S\)\(\sum_{i\in S}b_i=g_S\),于是 \(S\) 的答案即为 \(g_S+f_{\complement_US}\)。时间复杂度 \(O(nm\log m+m2^m)\),轻微卡常。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=(1<<25)+5,inf=1e18;
il int pls(int x,int y){
	return x+y<inf?x+y:inf;
}
il void add(int &x,int y){
	x=pls(x,y);
}
int n,m,b[30],f[maxn],g[maxn];
struct node{
	int p,v;
	il bool operator<(const node &x)const{
		return v<x.v;
	}
}a[30];
int main(){
	freopen("putin.in","r",stdin);
	freopen("putin.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[j].v;
			a[j].p=j;
		}
		sort(a+1,a+m+1);
		a[m+1]={m+1,inf};
		for(int j=1,S=0;j<=m+1;j++){
//			cout<<a[j].p<<' '<<a[j].v<<'\n';
			add(f[S],a[j].v-a[j-1].v);
			S|=1<<(a[j].p-1);
		}
	}
	for(int i=1;i<=m;i++){
		for(int S=0;S<1<<m;S++){
			if(S>>(i-1)&1){
				continue;
			}
			add(f[S|1<<(i-1)],f[S]);
		}
	}
	for(int i=1;i<=m;i++){
		cin>>b[i];
	}
	int ans=inf;
	for(int S=0;S<1<<m;S++){
		g[S]=pls(g[S^(S&-S)],b[__lg(S&-S)+1]);
		ans=min(ans,g[S]+f[((1<<m)-1)^S]);
	}
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}

C. 最长路径

重要结论:最长路径上相邻的点必然满足 \(|x_i-x_j|=1\lor|y_i-y_j|=1\)。显然成立。

考虑 DP。设 \(f_{i,j}\) 表示以 \((i,j)\) 为结尾的最长路径长度,\(g_{i,j}\) 表示以 \((i,j)\) 结尾的最长路径长度的数量,显然只会从左上方的一个 _| 形区域转移过来。如果我们能求出它的范围,就可以用单调队列优化了。

我们要求的就是 \(i\) 上一行的最左端的转移点,和前一列最上面的转移点,这里以最左端转移点为例。此时对于所有点 \(([r_1+1,r_2],[c_1+1,c_2])\),它们对应的转移点都应为 \(c_1\)。显然不能直接全部赋值,考虑只给 \(([r_1+1,r_2],c_2)\) 赋值,最后再做一遍后缀取 \(\min\)。时间仍然不正确,考虑扫描线,将所有矩形按 \(c_1\) 排序,给每一列开一个并查集,将已经赋过值的合并起来,这样在未来赋值时可以直接跳过这一段。每个位置最多被赋值一次,于是时间复杂度正确。

然后就是单队 DP 了。我们对于每一行和每一列都维护单调队列,再给每一行和每一列都开一个桶维护队列中 \(f\) 值为 \(x\)\(g\) 的总和即可。注意 \((i-1,j-1)\) 的贡献可能被计算两遍,减掉就好了。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e3+5,maxm=5e5+5,mod=1e9+7;
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);
}
il void chkmin(int &x,int y){
	x=min(x,y);
}
il void chkmax(int &x,int y){
	x=max(x,y);
}
int T,n,m,q,f[maxn][maxn],g[maxn][maxn];
int b1[maxn][maxn],b2[maxn][maxn];
int q1[maxn][maxn],hd1[maxn],tl1[maxn];
int q2[maxn][maxn],hd2[maxn],tl2[maxn];
int s1[maxn][maxn],s2[maxn][maxn];
struct jux{
	int x1,x2,y1,y2;
}a[maxm];
struct{
	int fa[maxn];
	il void init(int n){
		for(int i=1;i<=n;i++){
			fa[i]=i;
		}
	}
	il int find(int x){
		return x!=fa[x]?fa[x]=find(fa[x]):x;
	}
	il void merge(int u,int v){
		fa[find(u)]=find(v);
	}
}d[maxn];
il void solve(){
	cin>>n>>m>>q;
	for(int i=1;i<=q;i++){
		cin>>a[i].x1>>a[i].y1>>a[i].x2>>a[i].y2;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			b1[i][j]=j,b2[i][j]=i;
		}
	}
	sort(a+1,a+q+1,[](jux x,jux y){return x.y1<y.y1;});
	for(int i=1;i<=m;i++){
		d[i].init(n+1);
	}
	for(int i=1;i<=q;i++){
		int x1=a[i].x1,x2=a[i].x2,y1=a[i].y1,y2=a[i].y2;
		for(int j=d[y2].find(x1+1);j<=x2;j=d[y2].find(j)){
			chkmin(b1[j][y2],y1);
			d[y2].merge(j,j+1);
		}
	}
//	puts("***********************************************************");
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=m;j++){
//			cout<<b1[i][j]<<' ';
//		}
//		cout<<'\n';
//	}
//	puts("***********************************************************");
	sort(a+1,a+q+1,[](jux x,jux y){return x.x1<y.x1;});
	for(int i=1;i<=n;i++){
		d[i].init(m+1);
	}
	for(int i=1;i<=q;i++){
		int x1=a[i].x1,x2=a[i].x2,y1=a[i].y1,y2=a[i].y2;
		for(int j=d[x2].find(y1+1);j<=y2;j=d[x2].find(j)){
			chkmin(b2[x2][j],x1);
			d[x2].merge(j,j+1);
		}
	}
	for(int i=n;i;i--){
		for(int j=m;j;j--){
			if(j<m){
				chkmin(b1[i][j],b1[i][j+1]);
			}
			if(i<n){
				chkmin(b2[i][j],b2[i+1][j]);
			}
		}
	}
//	puts("***********************************************************");
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=m;j++){
//			cout<<b1[i][j]<<' ';
//		}
//		cout<<'\n';
//	}
//	puts("***********************************************************");
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=m;j++){
//			cout<<b2[i][j]<<' ';
//		}
//		cout<<'\n';
//	}
//	puts("***********************************************************");
	for(int i=0;i<n;i++){
		hd1[i]=1,tl1[i]=0;
	}
	for(int i=0;i<m;i++){
		hd2[i]=1,tl2[i]=0;
	}
	for(int i=0;i<=n;i++){
		for(int j=0;j<=m;j++){
			s1[i][j]=s2[j][i]=0;
		}
	}
	int ans1=0,ans2=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			while(hd1[i-1]<=tl1[i-1]&&f[i-1][j-1]>f[i-1][q1[i-1][tl1[i-1]]]){
				sub(s1[i-1][f[i-1][q1[i-1][tl1[i-1]]]],g[i-1][q1[i-1][tl1[i-1]]]);
				tl1[i-1]--;
			}
			add(s1[i-1][f[i-1][j-1]],g[i-1][j-1]);
			q1[i-1][++tl1[i-1]]=j-1;
			while(hd1[i-1]<=tl1[i-1]&&q1[i-1][hd1[i-1]]<b1[i][j]){
				sub(s1[i-1][f[i-1][q1[i-1][hd1[i-1]]]],g[i-1][q1[i-1][hd1[i-1]]]);
				hd1[i-1]++;
			}
			while(hd2[j-1]<=tl2[j-1]&&f[i-1][j-1]>f[q2[j-1][tl2[j-1]]][j-1]){
				sub(s2[j-1][f[q2[j-1][tl2[j-1]]][j-1]],g[q2[j-1][tl2[j-1]]][j-1]);
				tl2[j-1]--;
			}
			add(s2[j-1][f[i-1][j-1]],g[i-1][j-1]);
			q2[j-1][++tl2[j-1]]=i-1;
			while(hd2[j-1]<=tl2[j-1]&&q2[j-1][hd2[j-1]]<b2[i][j]){
				sub(s2[j-1][f[q2[j-1][hd2[j-1]]][j-1]],g[q2[j-1][hd2[j-1]]][j-1]);
				hd2[j-1]++;
			}
			if(hd1[i-1]>tl1[i-1]){
				if(hd2[j-1]>tl2[j-1]){
					f[i][j]=g[i][j]=1;
				}else{
					f[i][j]=f[q2[j-1][hd2[j-1]]][j-1]+1;
					g[i][j]=s2[j-1][f[q2[j-1][hd2[j-1]]][j-1]];
				}
			}else{
				if(hd2[j-1]>tl2[j-1]){
					f[i][j]=f[i-1][q1[i-1][hd1[i-1]]]+1;
					g[i][j]=s1[i-1][f[i-1][q1[i-1][hd1[i-1]]]];
				}else{
					int x=f[i-1][q1[i-1][hd1[i-1]]],y=f[q2[j-1][hd2[j-1]]][j-1];
					if(x>y){
						f[i][j]=x+1,g[i][j]=s1[i-1][x];
					}else if(x<y){
						f[i][j]=y+1,g[i][j]=s2[j-1][y];
					}else{
						f[i][j]=x+1,g[i][j]=pls(s1[i-1][x],s2[j-1][y]);
						if(f[i-1][j-1]==x){
							sub(g[i][j],g[i-1][j-1]);
						}
					}
				}
			}
			if(ans1<f[i][j]){
				ans1=f[i][j],ans2=g[i][j];
			}else if(ans1==f[i][j]){
				add(ans2,g[i][j]);
			}
		}
	}
//	puts("------------------------------------------------");
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=m;j++){
//			cout<<f[i][j]<<' ';
//		}
//		cout<<'\n';
//	}
//	puts("------------------------------------------------");
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=m;j++){
//			cout<<g[i][j]<<' ';
//		}
//		cout<<'\n';
//	}
//	puts("------------------------------------------------");
	cout<<ans1<<' '<<ans2<<'\n';
}
int main(){
	freopen("path.in","r",stdin);
	freopen("path.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		solve();
	}
	return 0;
}
}
int main(){return asbt::main();}
/*
1
7 9 3
1 4 4 4
1 3 4 5
3 1 5 4
3 8
*/

D. 生成树的传说

2025CSP-S模拟赛68

A B C D Sum Rank
100 37 40 25 202 5/14

A. 三角形

直接枚举 \(A\) 的所有 \(6\) 种状态,计算答案并取最小值即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
int n;
struct sjx{
	int a[15][15];
	il int*operator[](int x){
		return a[x];
	}
	il void xz(){
		sjx b;
		for(int i=1;i<=n;i++){
			for(int j=1,ii=n,jj=i;j<=i;j++,ii--,jj--){
				b[i][j]=a[ii][jj];
			}
		}
		*this=b;
	}
	il void dc(){
		for(int i=1;i<=n;i++){
			for(int l=1,r=i;l<=r;l++,r--){
				swap(a[i][l],a[i][r]);
			}
		}
	}
	il int operator-(sjx b)const{
		int res=0;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=i;j++){
				res+=a[i][j]^b[i][j];
			}
		}
		return res;
	}
}a,b;
int main(){
	freopen("triangle.in","r",stdin);
	freopen("triangle.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			cin>>a[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			cin>>b[i][j];
		}
	}
	int ans=a-b;
	a.dc(),ans=min(ans,a-b),a.dc();
	a.xz(),ans=min(ans,a-b);
	a.dc(),ans=min(ans,a-b),a.dc();
	a.xz(),ans=min(ans,a-b);
	a.dc(),ans=min(ans,a-b);
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=i;j++){
//			cout<<a[i][j]<<' ';
//		}
//		cout<<'\n';
//	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

B. 子段异或

假设第一个 \(1\) 的位置为 \(p\),我们一定要选的一个串是 \(f_S(p,n)\)。考虑其后第一段连续的 \(0\),我们希望把它们也变成 \(1\)。假设那一段 \(0\) 的开头是 \(q\),长度为 \(len\),我们希望其后的第二段 \(1\) 不受影响,也就是用这一段 \(0\) 来和它们匹配,于是第二个串的开头为 \(\max(p,q-len)\)。再特判一些 corner case 即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e7+5;
int T,n;
string s;
il bool chk1(){
	for(char i:s){
		if(i=='0'){
			return 0;
		}
	}
	return 1;
}
il bool chk0(){
	for(char i:s){
		if(i=='1'){
			return 0;
		}
	}
	return 1;
}
int main(){
	freopen("xor.in","r",stdin);
	freopen("xor.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>s;
		if(chk1()){
			cout<<string(n-1,'1')<<0<<'\n';
			continue;
		}
		if(chk0()){
			cout<<0<<'\n';
			continue;
		}
		int p=0;
		while(p<n&&s[p]=='0'){
			p++;
		}
		int q=p;
		while(q<n&&s[q]=='1'){
			q++;
		}
		if(q==n){
			cout<<string(n-p,'1')<<'\n';
			continue;
		}
		int t=q;
		while(t<n&&s[t]=='0'){
			t++;
		}
		string a=s.substr(p,n-p);
		string b=string(q-p,'0')+s.substr(max(2*q-t,p),n-q);
		for(int i=0;i<a.size();i++){
			cout<<(int)(a[i]^b[i]);
		}
		cout<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

C. 三等分

\(a_i\) 表示 \(i\) 的个数。不妨只考虑 \((x,x+1,x+2)\) 三元组,将所有 \(a_i\) 变为三的倍数。

\(f_{i,x,y}\) 表示考虑到 \(i\),有 \(x\)\((i,i+1,i+2)\)\(y\)\((i-1,i,i+1)\) 的方案数。于是有转移:

\[f_{i,x,y}=\sum_{x+y+z\le a_i\land x+y+z\equiv a_i\pmod{3}}f_{i-1,y,z} \]

时空复杂度都是 \(O(n^3)\)。滚动数组 + 前缀和优化即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e3+5,mod=1e9+7;
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],f[2][maxn][maxn],g[maxn][maxn][3];
int main(){
	freopen("three.in","r",stdin);
	freopen("three.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1,x;i<=n;i++){
		cin>>x;
		a[x]++;
	}
	f[0][0][0]=g[0][0][0]=1;
	for(int i=1,p=1,q=0;i<=m;i++,p^=1,q^=1){
		for(int x=0,xx=min({a[i],a[i+1],a[i+2]});x<=xx;x++){
			for(int y=0,yy=min({a[i]-x,a[i+1]-x,a[i-1]});y<=yy;y++){
				f[p][x][y]=g[y][min({a[i]-x-y,a[i-1]-y,i>1?a[i-2]:0})][(a[i]-x-y)%3];
			}
		}
		for(int x=0,xx=min({a[i],a[i+1],a[i+2]});x<=xx;x++){
			for(int y=0,yy=min({a[i]-x,a[i+1]-x,a[i-1]});y<=yy;y++){
				g[x][y][0]=y?g[x][y-1][0]:0;
				g[x][y][1]=y?g[x][y-1][1]:0;
				g[x][y][2]=y?g[x][y-1][2]:0;
				add(g[x][y][y%3],f[p][x][y]);
			}
		}
	}
	cout<<f[m&1][0][0];
	return 0;
}
}
int main(){return asbt::main();}

D. 二叉树遍历

答辩,我跟你爆了。

我们要求的是在 \(x\) 前面遍历到的 \(y\) 的数量。考虑哪些 \(y\) 能产生贡献(\(op_x\) 表示 \(x\) 的遍历方式,\(tr_x\) 表示 \(x\) 的子树,\(ls_x,rs_x\) 表示 \(x\) 的左右子树):

  • \(op_x=0,y\in ls_x\)
  • \(op_x=1,y\in tr_x\)
  • \(\exist z,y\in ls_z,x\in rs_z\)
  • \(op_y=-1,x\in tr_y\)
  • \(op_y=0,x\in rs_y\)

前两类可以直接 \(O(1)\) 求,第三类能预处理出来。后两类不是非常好维护,考虑按编号分块。可以将块分成两类:第一类为整个块的遍历方式相同的块,第二类为整个块遍历方式不同的块。

对于第一类,我们只需要知道 \(x\) 是这个块内多少个点的子树/右子树,也可以预处理。考虑用数据结构维护第二类块的贡献。

对于区间推平操作,会将中间的整块变成一类块,两端的散块变成二类块。由于二类块最多一共只有 \(O(n)\) 个,考虑直接遍历内部的点在数据结构上修改。这样我们会修改 \(O(n\sqrt{n})\) 次,查询 \(O(n)\) 次,再按 dfn 分块,将区间加改为差分即可。时间复杂度 \(O(n\sqrt{n})\)

Code
// 答辩,我跟你爆了 
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e5+5,B=320,maxb=325;
int n,m,ls[maxn],rs[maxn],szl[maxn];
int dfn[maxn],cnt,stk[maxn],sz[maxn];
int bnm,bg[maxb],ed[maxb],bel[maxn];
il void dfs(int u,int x){
	if(!u){
		return ;
	}
	dfn[u]=++cnt,stk[cnt]=u,szl[u]=x;
	dfs(ls[u],x),dfs(rs[u],x+sz[ls[u]]);
	sz[u]=sz[ls[u]]+sz[rs[u]]+1;
}
namespace B2{
	/*
	just a declaration
	*/
	il void add(int,int,int);
	il int query(int);
} 
namespace B1{
	/*
	这个分块的对象是 y,按照编号分块 
	需要记录每一块的类型,以及 1 类块的标记和所有位置的标记 
	支持的操作是区间修改,和查询 
	需要调用 B2(?有毒吧) 
	*/
	int cnts[maxn][maxb]       ,cntr[maxn][maxb];
	// i 是 j 块中多少点的子树,i 是 j 块中多少点的右子树 
	int typ[maxb],opt[maxn],tag[maxb];
	il void dfs(int u){
		for(int i=1;i<=bnm;i++){
			if(ls[u]){
				cnts[ls[u]][i]+=cnts[u][i];
				cntr[ls[u]][i]+=cntr[u][i];
			}
			if(rs[u]){
				cnts[rs[u]][i]+=cnts[u][i];
				cntr[rs[u]][i]+=cntr[u][i];
			}
		}
		if(ls[u]){
			cnts[ls[u]][bel[u]]++;
			dfs(ls[u]);
		}
		if(rs[u]){
			cnts[rs[u]][bel[u]]++;
			cntr[rs[u]][bel[u]]++;
			dfs(rs[u]);
		}
	}
	il void build(){
		dfs(1);
		for(int i=1;i<=n;i++){
			tag[i]=-1;
		}
		for(int i=1;i<=bnm;i++){
			typ[i]=1;
		}
	}
	il void upd(int l,int r,int x){
		int pl=bel[l],pr=bel[r];
		if(pl==pr){
			if(typ[pl]==1){
				typ[pl]=2;
				for(int i=bg[pl];i<=ed[pl];i++){
//					cout<<"f "<<__LINE__<<' '<<i<<'\n';
					if(tag[pl]==-1){
						B2::add(dfn[i]+1,dfn[i]+sz[i]-1,1);
					}else if(tag[pl]==0){
						B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,1);
					}
					opt[i]=tag[pl];
				}
			}
			for(int i=l;i<=r;i++){
//				cout<<"f "<<__LINE__<<' '<<i<<'\n';
				if(opt[i]==-1){
					B2::add(dfn[i]+1,dfn[i]+sz[i]-1,-1);
				}else if(opt[i]==0){
					B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,-1);
				}
				opt[i]=x;
				if(opt[i]==-1){
					B2::add(dfn[i]+1,dfn[i]+sz[i]-1,1);
				}else if(opt[i]==0){
					B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,1);
				}
			}
			return ;
		}
		for(int i=pl+1;i<pr;i++){
//			cout<<"f "<<__LINE__<<' '<<i<<'\n';
			if(typ[i]==2){
				for(int j=bg[i];j<=ed[i];j++){
//					cout<<"f "<<__LINE__<<' '<<j<<'\n';
					if(opt[j]==-1){
						B2::add(dfn[j]+1,dfn[j]+sz[j]-1,-1);
					}else if(opt[j]==0){
						B2::add(dfn[j]+sz[ls[j]]+1,dfn[j]+sz[j]-1,-1);
					}
				}
				typ[i]=1;
			}
			tag[i]=x;
		}
		if(typ[pl]==1){
			typ[pl]=2;
			for(int i=bg[pl];i<=ed[pl];i++){
//				cout<<"f "<<__LINE__<<' '<<i<<'\n';
				if(tag[pl]==-1){
					B2::add(dfn[i]+1,dfn[i]+sz[i]-1,1);
				}else if(tag[pl]==0){
					B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,1);
				}
				opt[i]=tag[pl];
			}
		}
		for(int i=l;i<=ed[pl];i++){
//			cout<<"f "<<__LINE__<<' '<<i<<'\n';
			if(opt[i]==-1){
				B2::add(dfn[i]+1,dfn[i]+sz[i]-1,-1);
			}else if(opt[i]==0){
				B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,-1);
			}
			opt[i]=x;
			if(opt[i]==-1){
				B2::add(dfn[i]+1,dfn[i]+sz[i]-1,1);
			}else if(opt[i]==0){
				B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,1);
			}
		}
		if(typ[pr]==1){
			typ[pr]=2;
			for(int i=bg[pr];i<=ed[pr];i++){
//				cout<<"f "<<__LINE__<<' '<<i<<'\n';
				if(tag[pr]==-1){
					B2::add(dfn[i]+1,dfn[i]+sz[i]-1,1);
				}else if(tag[pr]==0){
					B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,1);
				}
				opt[i]=tag[pr];
			}
		}
		for(int i=bg[pr];i<=r;i++){
//			cout<<"f "<<__LINE__<<' '<<i<<'\n';
			if(opt[i]==-1){
				B2::add(dfn[i]+1,dfn[i]+sz[i]-1,-1);
			}else if(opt[i]==0){
				B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,-1);
			}
			opt[i]=x;
			if(opt[i]==-1){
				B2::add(dfn[i]+1,dfn[i]+sz[i]-1,1);
			}else if(opt[i]==0){
				B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,1);
			}
		}
	}
	il int query(int x){
//		for(int i=1;i<=bnm;i++){
//			cout<<typ[i]<<' ';
//		}
//		cout<<'\n';
		int res=B2::query(dfn[x])+szl[x]+1;
		int optx=typ[bel[x]]==1?tag[bel[x]]:opt[x];
		if(optx==0){
			res+=sz[ls[x]];
		}else if(optx==1){
			res+=sz[x]-1;
		}
		for(int i=1;i<=bnm;i++){
			if(typ[i]==1){
				if(tag[i]==-1){
					res+=cnts[x][i];
				}else if(tag[i]==0){
					res+=cntr[x][i];
				}
			}
		}
		return res;
	}
}
namespace B2{
	/*
	implementation
	用来维护二类块的贡献,是按照 dfn 分块的 
	区间加、单点查,但为了平衡时间改为差分 
	*/
	int tag[maxb][maxb];
	il void add(int l,int r,int x){
		if(l>r){
			return ;
		}
//		cout<<"a "<<l<<' '<<r<<' '<<x<<'\n';
		int pl=bel[l],pr=bel[r];
		if(pl==pr){
			tag[pl][l-bg[pl]+1]+=x,tag[pl][r-bg[pl]+2]-=x;
			return ;
		}
		tag[pl+1][0]+=x,tag[pr][0]-=x;
		tag[pl][l-bg[pl]+1]+=x,tag[pl][ed[pl]-bg[pl]+2]-=x;
		tag[pr][1]+=x,tag[pr][r-bg[pr]+2]-=x;
	}
	il int query(int x){
		int res=0;
		for(int i=1;i<=bel[x];i++){
//			cout<<tag[i][0]<<' ';
			res+=tag[i][0];
		}
//		cout<<'\n';
//		cout<<"p "<<res<<'\n';
		for(int i=1;i<=x-bg[bel[x]]+1;i++){
			res+=tag[bel[x]][i];
//			cout<<tag[bel[x]][i]<<' ';
		}
//		cout<<'\n';
//		cout<<"q "<<res<<'\n';
		return res;
	}
}
int main(){
	freopen("traversing.in","r",stdin);
	freopen("traversing.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>ls[i]>>rs[i];
	}
	dfs(1,0);
//	for(int i=1;i<=n;i++){
//		cout<<i<<' '<<sz[i]<<' '<<ls[i]<<' '<<rs[i]<<' '<<szl[i]<<'\n';
//	}
	bnm=n/B;
	for(int i=1;i<=bnm;i++){
		bg[i]=ed[i-1]+1,ed[i]=ed[i-1]+B;
	}
	if(ed[bnm]<n){
		bg[bnm+1]=ed[bnm]+1,ed[bnm+1]=n;
		bnm++;
	}
	for(int i=1;i<=bnm;i++){
		for(int j=bg[i];j<=ed[i];j++){
			bel[j]=i;
		}
	}
	B1::build();
	while(m--){
		int opt;
		cin>>opt;
		if(opt==1){
			int l,r,x;
			cin>>l>>r>>x;
			B1::upd(l,r,x);
		}else{
			int x;
			cin>>x;
			cout<<B1::query(x)<<'\n';
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

2025CSP-S模拟赛72

A B C D Sum Rank
100 100 20 20 240 2/7

为啥还在打 CSP 模拟赛啊

A. 翻牌

二分答案 \(mid\),将 \(\ge mid\) 的设为 \(1\)\(<mid\) 的设为 \(-1\),则当且仅当和 \(>0\)\(ans\ge mid\)。找出 \(b_i-a_i\) 的最大子段和即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=3e5+5;
int n,a[maxn],b[maxn],c[maxn],d[maxn];
il bool check(int x){
	int sum=0,mx=0;
	for(int i=1;i<=n;i++){
		sum+=a[i]>=x?1:-1;
		c[i]=c[i-1]+(b[i]>=x?1:-1)-(a[i]>=x?1:-1);
		mx=max(mx,c[i]-d[i-1]);
		d[i]=min(d[i-1],c[i]);
	}
	return sum+mx>0;
}
int main(){
//	freopen("flip.in","r",stdin);
//	freopen("flip.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];
	}
	int l=1,r=1e9;
	while(l<r){
		int mid=(l+r+1)>>1;
		if(check(mid)){
			l=mid;
		}else{
			r=mid-1;
		}
	}
	cout<<l;
	return 0;
}
}
int main(){return asbt::main();}

B. 塔

考虑如果知道了这个序列,怎么求最小操作次数:从左往右扫,如果 \(a_i>a_{i-1}\),则要增加 \(a_i-a_{i-1}\) 次,否则不用增加。于是设 \(f_{i,j}\) 表示考虑了前 \(i\) 个,\(a_i=j\) 的最小操作次数,于是有转移:

\[f_{i,j}=\min(\min_{k=0}^{j-d_{i-1}}\{f_{i-1,k}+j-k\},\min_{k=j+d_{i-1}}^{10^5}\{f_{i-1,k}\}) \]

前后缀最小值优化即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=105,maxm=1e5+5,inf=1e9,B=1e5;
int n,a[maxn],f[maxn][maxm],pre[maxm],suf[maxm];
int main(){
//	freopen("tower.in","r",stdin);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=2;i<=n;i++){
		cin>>a[i];
	}
	memset(f,0x3f,sizeof(f));
	f[0][0]=0;
	for(int i=1;i<=n;i++){
		pre[0]=f[i-1][0];
		for(int j=1;j<=B;j++){
			pre[j]=min(pre[j-1],f[i-1][j]-j);
		}
		suf[B]=f[i-1][B];
		for(int j=B-1;~j;j--){
			suf[j]=min(suf[j+1],f[i-1][j]);
		}
		for(int j=0;j<=B;j++){
			f[i][j]=min(j<a[i]?inf:pre[j-a[i]]+j,j+a[i]>B?inf:suf[j+a[i]]);
		}
	}
	int ans=inf;
	for(int i=0;i<=B;i++){
		ans=min(ans,f[n][i]);
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

C. 图

设点集大小为 \(k\),于是由于最大度数不超过 \(3\),边数最多为 \(\frac{3}{2}k\)。由于要求红、蓝均连通,边数至少为 \(2(k-1)\)。于是有不等式:\(\frac{3}{2}k\ge2(k-1)\),解集为 \(k\le4\)。于是我们只需要考虑 \(k\in[1,4]\) 的情况。

于是直接枚举所有可能合法的红边连通的点集。以每个点为根跑出一棵 dfs 树即可。考虑如果一个点连了三条边,那么这个点集不可能蓝边连通,于是这个点集在搜索树上必然是一条根链。会算重,用 set 去重即可。时间复杂度 \(O(n\log n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,m,fa[maxn],faz[maxn],p[maxn];
bool vis[maxn],pp[maxn];
vector<int> e[2][maxn];
il int find(int x){
	return x!=fa[x]?fa[x]=find(fa[x]):x;
}
struct node{
	int a[4];
	node(){}
	node(int u){
		a[0]=a[1]=a[2]=a[3]=0;
		int p=0;
		while(u){
//			cout<<u<<'\n';
			a[p++]=u,u=faz[u];
		}
		sort(a,a+4);
//		for(int i=0;i<=3;i++){
//			cout<<a[i]<<' ';
//		}
//		cout<<'\n';
	}
	il bool operator<(const node &x)const{
		if(a[0]!=x.a[0]){
			return a[0]<x.a[0];
		}
		if(a[1]!=x.a[1]){
			return a[1]<x.a[1];
		}
		if(a[2]!=x.a[2]){
			return a[2]<x.a[2];
		}
		return a[3]<x.a[3];
	}
};
set<node> ans;
il void dfs(int u,int fth,int dep){
//	cout<<u<<'\n';
	if(dep>4){
		return ;
	}
	faz[u]=fth,vis[u]=1;
	int x=u,cnt=0;
	while(x){
		p[++cnt]=x,x=faz[x];
	}
	for(int i=1;i<=cnt;i++){
		fa[p[i]]=p[i],pp[p[i]]=1;
	}
	for(int i=1;i<=cnt;i++){
		int u=p[i];
		for(int v:e[1][u]){
			if(pp[v]){
				fa[find(u)]=find(v);
			}
		}
	}
	bool flag=0;
//	cout<<cnt<<'\n';
	for(int i=1;i<=cnt;i++){
//		cout<<p[i]<<' '<<find(p[i])<<'\n';
		if(find(p[i])==p[i]){
			if(!flag){
				flag=1;
			}else{
				goto togo;
			}
		}
	}
	ans.insert(node(u));
//	puts("666");
	togo:;
	for(int i=1;i<=cnt;i++){
		pp[p[i]]=0;
	}
	for(int v:e[0][u]){
		if(!vis[v]){
			dfs(v,u,dep+1);
		}
	}
	vis[u]=0;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1,u,v,c;i<=m;i++){
		cin>>u>>v>>c;
		e[c][u].pb(v),e[c][v].pb(u);
	}
	for(int i=1;i<=n;i++){
//		cout<<i<<'\n';
		dfs(i,0,1);
	}
	cout<<ans.size();
	return 0;
}
}
int main(){return asbt::main();}

D. 勇士斗恶龙

首先考虑 \(b_i\) 都为 \(0\) 的情况,设 \(dp_i\) 表示恶龙血量为 \(i\) 的答案,有转移:

\[dp_i=\sum_{j=1}^{100}dp_{i-j}cnt_j \]

其中 \(cnt_j\) 表示 \(a_i=j\) 的法术的数量。设 \(ans_i=\begin{bmatrix} dp_i&dp_{i-1}&\cdots&dp_{i-99} \end{bmatrix}\),于是可以设计出转移矩阵:

\[ans_i=ans_{i-1}\times\begin{bmatrix} cnt_1&1&0&\cdots&0\\ cnt_2&0&1&\cdots&0\\ \vdots&\vdots&\vdots&\ddots&\vdots\\ cnt_{99}&0&0&\cdots&1\\ cnt_{100}&0&0&\cdots&0 \end{bmatrix} \]

矩阵快速幂即可。

考虑 \(b_i\) 的限制,有新的转移方程:

\[f_i=\sum_{j=1}^{100}\sum_{k=0}^{60}[i\equiv0\pmod{2^k}]f_{i-j}cnt_{j,k} \]

其中 \(cnt_{j,k}\) 表示 \(a_i=j,b_i=k\) 的法术的数量。

考虑依然用矩阵优化,将 \(n\) 拆位,我们希望求出 \(f_i\) 表示 \(0\)\(2^i\) 的转移矩阵,因为较高的幂次不会影响低幂次的转移,所以有:

\[ans_n=\prod_{i=60}^{0}[\operatorname{bit}_i(n)=1]f_i \]

其中 \(\operatorname{bit}_i(n)\) 表示 \(n\) 在二进制下的第 \(i\) 位。

考虑求 \(f_i\)。设 \(bas_i\) 表示考虑了 \(2\)\(0\sim i\) 次幂的法术的转移矩阵,\(g_i\) 表示 \(0\)\(2^i-1\) 的转移矩阵,于是有:

\[\begin{cases} g_i=f_{i-1}\times g_{i-1}\\ f_i=g_i\times bas_i \end{cases} \]

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int mod=1e9+7;
int m;
ll n;
struct juz{
	int a[105][105];
	juz(){
		memset(a,0,sizeof(a));
	}
	il int*operator[](int x){
		return a[x];
	}
	il juz operator*(juz b)const{
		juz c;
		for(int i=1;i<=100;i++){
			for(int j=1;j<=100;j++){
				for(int k=1;k<=100;k++){
					c[i][j]=(a[i][k]*1ll*b[k][j]+c[i][j])%mod;
				}
			}
		}
		return c;
	}
}bas[65],f[65],g[65],ans;
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1,a,b;i<=m;i++){
		cin>>a>>b;
		do{
			bas[b++][a][1]++;
		}while(b<=60);
	}
	for(int i=1;i<100;i++){
		for(int j=0;j<=60;j++){
			bas[j][i][i+1]=1;
		}
	}
	f[0]=bas[0];
	for(int i=1;i<=100;i++){
		g[0][i][i]=ans[i][i]=1;
	}
	for(int i=1;i<=60;i++){
		g[i]=f[i-1]*g[i-1];
		f[i]=g[i]*bas[i];
	}
	for(int i=60;~i;i--){
		if(n>>i&1){
			ans=ans*f[i];
		}
	}
	cout<<ans[1][1];
	return 0;
}
}
int main(){return asbt::main();}

HZOJ NOIP2025模拟1

A B C D Sum Rank
30 40 50 50 170 8/27

A. 公司的供应链

题意是要不断删环直到图变成 DAG。

暴力的做法是显然的,每次找出一个环然后删掉即可,时间复杂度 \(O(m^2)\)。它的问题在于每条边会重复走许多次,不够优秀。

考虑当找到了一个环时,先将它回溯出来,然后从这个环的起点(也就是 dfs 序最小的点)开始继续向下搜索。那么直接记录每个点上一次遍历到了第几条边即可。这样每条边只会遍历一次,时间复杂度 \(O(m)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
ifstream cin("dag.in");
ofstream cout("dag.out");
const int maxn=3e5+5,maxm=6e5+5;
int n,m,enm,hd[maxn];
bool vis[maxn],ban[maxm];
struct{
	int u,v,nxt;
}e[maxm];
il void addedge(int u,int v){
	e[++enm]={u,v,hd[u]};
	hd[u]=enm;
}
il int dfs(int u){
//	cout<<u<<'\n';
	vis[u]=1;
	for(int &i=hd[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(vis[v]){
			ban[i]=1,vis[u]=0,i=e[i].nxt;
			return v;
		}
		int x=dfs(v);
		if(x){
			ban[i]=1;
			if(u!=x){
				vis[u]=0,i=e[i].nxt;
				return x;
			}
		}
	}
	vis[u]=0;
	return 0;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		addedge(u,v);
	}
	for(int i=1;i<=n;i++){
		dfs(i);
	}
	int cnt=0;
	for(int i=1;i<=m;i++){
		if(!ban[i]){
			cnt++;
		}
	}
	cout<<cnt<<'\n';
	for(int i=1;i<=m;i++){
		if(!ban[i]){
			cout<<e[i].u<<' '<<e[i].v<<'\n';
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

B. 宇宙的卷积

考虑对于每个 \(i\) 求出 \(\max\limits_{(k-i)\subseteq j\subseteq k}b_j\)

\(f_{i,j}(j\subseteq\overline{i})\) 表示满足 \(x\cap\overline{i}=j\)\(x\)\(b_x\) 的最大值,于是 \(i\)\(k\) 的贡献为 \(a_i+f_{i,k-i}\)。考虑转移,当我们要从 \(S\) 转移到 \(T=S\cup\{i\}\) 时,有:

\[f_{T,X}=\max(f_{S,X},f_{S,X\cup\{i\}}) \]

时间复杂度 \(O(3^n)\)。只维护搜索树根链上的 \(f\) 值,空间复杂度 \(O(n2^n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=(1<<18)+5;
int n,a[maxn],f[20][maxn],ans[maxn];
il void dfs(int S,int d,int p){
//	cout<<(bitset<3>)S<<'\n';
	for(int T=S;T<1<<n;T=(T+1)|S){
		ans[T]=max(ans[T],a[S]+f[d][T^S]);
//		cout<<(bitset<3>)T<<' ';
	}
//	cout<<'\n';
	for(int i=p;i<n;i++){
		int T=S|1<<i,U=((1<<n)-1)^T;
		for(int X=U;;X=(X-1)&U){
			f[d+1][X]=max(f[d][X],f[d][X|1<<i]);
			if(!X){
				break;
			}
		}
		dfs(T,d+1,i+1);
		for(int X=U;;X=(X-1)&U){
			f[d+1][X]=0;
			if(!X){
				break;
			}
		}
	}
}
int main(){
	freopen("juanji.in","r",stdin);
	freopen("juanji.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=0;i<1<<n;i++){
		cin>>a[i];
	}
	for(int i=0;i<1<<n;i++){
		cin>>f[0][i];
	}
	dfs(0,0,0);
	for(int i=0;i<1<<n;i++){
		cout<<ans[i]<<' ';
	}
	return 0;
}
}
int main(){return asbt::main();}

C. 舰队的远征

整条路线可分为从 \(s\) 走到 \(x\),从 \(x\) 跳到 \(y\),再从 \(y\) 走到 \(t\) 三段。于是我们先用 dijkstra 跑出 \(s\) 到每个点的最短路 \(dis_i\) 和每个点到 \(t\) 的最短路 \(dit_i\)。于是对于 \((x,y)\),答案即为:

\[\begin{aligned} &dis_x+(x-y)^2+dit_y\\ =&(-2xy+x^2+dis_x)+y^2+dit_y \end{aligned} \]

括号内的是一条关于 \(y\) 的直线,使用李超线段树即可。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
ifstream cin("far.in");
ofstream cout("far.out");
const int maxn=2e5+5,inf=1e18;
int n,m,s,t,dis[maxn],dit[maxn];
bool vis[maxn];
vector<pii> es[maxn],et[maxn];
priority_queue<pii> q;
il void dijkstra(int x,vector<pii> *e,int *dis){
	for(int i=1;i<=n;i++){
		dis[i]=inf,vis[i]=0;
	}
	dis[x]=0,q.push(mp(0,x));
	while(q.size()){
		int u=q.top().sec;
		q.pop();
		if(vis[u]){
			continue;
		}
		vis[u]=1;
		for(pii i:e[u]){
			int v=i.fir,w=i.sec;
			if(!vis[v]&&dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				q.push(mp(-dis[v],v));
			}
		}
	}
}
struct line{
	int k,b;
	line(int k=0,int b=inf):k(k),b(b){}
	il int calc(int x){
		return k*x+b;
	}
}tr[maxn<<2];
il void insert(int id,int l,int r,line x){
	int mid=(l+r)>>1;
	if(tr[id].calc(mid)>x.calc(mid)){
		swap(tr[id],x);
	}
	int lo=tr[id].calc(l),ro=tr[id].calc(r);
	int ln=x.calc(l),rn=x.calc(r);
	if(ln<lo){
		insert(lid,l,mid,x);
	}else if(rn<ro){
		insert(rid,mid+1,r,x);
	}
}
il int query(int id,int l,int r,int p){
	if(l==r){
		return tr[id].calc(p);
	}
	int mid=(l+r)>>1,res=tr[id].calc(p);
	if(p<=mid){
		res=min(res,query(lid,l,mid,p));
	}else{
		res=min(res,query(rid,mid+1,r,p));
	}
	return res;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>s>>t;
	for(int i=1,u,v,w;i<=m;i++){
		cin>>u>>v>>w;
		es[u].pb(mp(v,w));
		et[v].pb(mp(u,w));
	}
	dijkstra(s,es,dis);
	dijkstra(t,et,dit);
//	for(int i=1;i<=n;i++){
//		cout<<i<<' '<<dis[i]<<' '<<dit[i]<<'\n';
//	}
	for(int i=1;i<=n;i++){
		insert(1,1,n,line(-2*i,dis[i]+i*i));
	}
	int ans=inf;
	for(int i=1;i<=n;i++){
		ans=min(ans,query(1,1,n,i)+dit[i]+i*i);
	}
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}

D. 军团的阵列线

\(\min\) 前面都是负号,因此我们可以依次给每个序列取相反数,这样就都求 \(\max\) 即可。于是我们要求的就是这个式子:

\[\sum_{l=1}^{n}\sum_{r=l}^{n}\max_{i=l}^{r}\{A_i\}\times\max_{i=l}^{r}\{B_i\}\times\max_{i=l}^{r}\{C_i\} \]

考虑扫描线右端点,用线段树维护这个式子。最大值用单调栈维护,然后在线段树上维护 \(A,B,C,AB,AC,BC,ABC\) 即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define lid id<<1
#define rid id<<1|1
#define a(id) tr[id].a
#define b(id) tr[id].b
#define c(id) tr[id].c
#define ab(id) tr[id].ab
#define ac(id) tr[id].ac
#define bc(id) tr[id].bc
#define abc(id) tr[id].abc
#define ta(id) tr[id].ta
#define tb(id) tr[id].tb
#define tc(id) tr[id].tc
using namespace std;
namespace asbt{
ifstream cin("team.in");
ofstream cout("team.out");
const int maxn=1e5+5;
int n,a[maxn],b[maxn],c[maxn];
int sa[maxn],sb[maxn],sc[maxn];
struct{
	unsigned int a,b,c,ab,ac,bc,abc,ta,tb,tc;
}tr[maxn<<2];
il void pushup(int id){
	a(id)=a(lid)+a(rid);
	b(id)=b(lid)+b(rid);
	c(id)=c(lid)+c(rid);
	ab(id)=ab(lid)+ab(rid);
	ac(id)=ac(lid)+ac(rid);
	bc(id)=bc(lid)+bc(rid);
	abc(id)=abc(lid)+abc(rid);
}
il void pushta(int id,int l,int r,unsigned int x){
	ta(id)+=x,a(id)+=x*(r-l+1);
	ab(id)+=x*b(id),ac(id)+=x*c(id),abc(id)+=x*bc(id);
}
il void pushtb(int id,int l,int r,unsigned int x){
	tb(id)+=x,b(id)+=x*(r-l+1);
	ab(id)+=x*a(id),bc(id)+=x*c(id),abc(id)+=x*ac(id);
}
il void pushtc(int id,int l,int r,unsigned int x){
	tc(id)+=x,c(id)+=x*(r-l+1);
	ac(id)+=x*a(id),bc(id)+=x*b(id),abc(id)+=x*ab(id);
}
il void pushdown(int id,int l,int r){
	int mid=(l+r)>>1;
	if(ta(id)){
		pushta(lid,l,mid,ta(id));
		pushta(rid,mid+1,r,ta(id));
		ta(id)=0;
	}
	if(tb(id)){
		pushtb(lid,l,mid,tb(id));
		pushtb(rid,mid+1,r,tb(id));
		tb(id)=0;
	}
	if(tc(id)){
		pushtc(lid,l,mid,tc(id));
		pushtc(rid,mid+1,r,tc(id));
		tc(id)=0;
	}
}
il void build(int id,int l,int r){
	tr[id]={0,0,0,0,0,0,0,0,0,0};
	if(l==r){
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
}
il void adda(int id,int L,int R,int l,int r,unsigned int x){
//	cout<<"a "<<id<<' '<<L<<' '<<R<<' '<<l<<' '<<r<<' '<<x<<'\n';
	if(L>=l&&R<=r){
		pushta(id,L,R,x);
		return ;
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1;
	if(l<=mid){
		adda(lid,L,mid,l,r,x);
	}
	if(r>mid){
		adda(rid,mid+1,R,l,r,x);
	}
	pushup(id);
}
il void addb(int id,int L,int R,int l,int r,unsigned int x){
//	cout<<"b "<<id<<' '<<L<<' '<<R<<' '<<l<<' '<<r<<' '<<x<<'\n';
	if(L>=l&&R<=r){
		pushtb(id,L,R,x);
		return ;
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1;
	if(l<=mid){
		addb(lid,L,mid,l,r,x);
	}
	if(r>mid){
		addb(rid,mid+1,R,l,r,x);
	}
	pushup(id);
}
il void addc(int id,int L,int R,int l,int r,unsigned int x){
//	cout<<"c "<<id<<' '<<L<<' '<<R<<' '<<l<<' '<<r<<' '<<x<<'\n';
	if(L>=l&&R<=r){
		pushtc(id,L,R,x);
		return ;
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1;
	if(l<=mid){
		addc(lid,L,mid,l,r,x);
	}
	if(r>mid){
		addc(rid,mid+1,R,l,r,x);
	}
	pushup(id);
}
il unsigned int solve(){
	unsigned int ans=0;
	int tpa=0,tpb=0,tpc=0;
	build(1,1,n);
	for(int i=1;i<=n;i++){
		while(tpa&&a[i]>a[sa[tpa]]){
			adda(1,1,n,sa[tpa-1]+1,sa[tpa],a[i]-a[sa[tpa]]);
			tpa--;
		}
		while(tpb&&b[i]>b[sb[tpb]]){
			addb(1,1,n,sb[tpb-1]+1,sb[tpb],b[i]-b[sb[tpb]]);
			tpb--;
		}
		while(tpc&&c[i]>c[sc[tpc]]){
			addc(1,1,n,sc[tpc-1]+1,sc[tpc],c[i]-c[sc[tpc]]);
			tpc--;
		}
		sa[++tpa]=sb[++tpb]=sc[++tpc]=i;
		adda(1,1,n,i,i,a[i]);
		addb(1,1,n,i,i,b[i]);
		addc(1,1,n,i,i,c[i]);
		ans+=abc(1);
//		cout<<i<<' '<<abc(1)<<'\n';
	}
//	cout<<ans<<'\n';
	return ans;
}
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=1;i<=n;i++){
		cin>>c[i];
	}
	unsigned int ans=0;
	for(int S=0;S<8;S++){
		if(S&0b1){
			for(int i=1;i<=n;i++){
				a[i]=-a[i];
			}
		}
		if(S&0b10){
			for(int i=1;i<=n;i++){
				b[i]=-b[i];
			}
		}
		if(S&0b100){
			for(int i=1;i<=n;i++){
				c[i]=-c[i];
			}
		}
//		cout<<bitset<3>(S)<<'\n';
		ans+=solve();
		if(S&0b1){
			for(int i=1;i<=n;i++){
				a[i]=-a[i];
			}
		}
		if(S&0b10){
			for(int i=1;i<=n;i++){
				b[i]=-b[i];
			}
		}
		if(S&0b100){
			for(int i=1;i<=n;i++){
				c[i]=-c[i];
			}
		}
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

HZOJ NOIP2025模拟2

A B C D Sum Rank
100 0 40 60 200 10/29

A. 数字(math)

不妨令 \(n\le m\),于是有 \(a\times b=a(c\times10^{m-n}+d)\),其中 \(\lg a=\lg c\)。于是我们优先提升 \(ac\),再提升 \(ad\)

显然要将所有数码从大到小往里填,因此 \(a+c\) 是定值,我们要使 \(|a-c|\) 尽可能的小。考虑如果 \(a\) 更大,则会顺带使 \(ad\) 更大,而 \(c\) 更大则不会有额外的贡献,因此要求 \(a\ge c\)。于是我们每次取出前两大的数码填,如果此前 \(a=c\) 则将较大的填入 \(a\),否则此时已经满足 \(a>c\) 了,将较大的填入 \(c\)。最后再将剩下的数码倒序填入 \(d\) 即可。

需要高精乘,时间复杂度 \(O(nm)\)

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
ifstream cin("math.in");
ofstream cout("math.out");
const int maxn=2e3+5;
int T,n,m,p[maxn],c[maxn];
int main(){
//	system("fc ex_math2.out my.out");
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>m;
		if(n>m){
			swap(n,m);
		}
		int cnt=0;
		for(int i=1,x;i<=9;i++){
			cin>>x;
			while(x--){
				p[++cnt]=i;
			}
		}
		if(!n){
			cout<<0<<'\n';
			continue;
		}
		string a,b;
		bool flag=0;
		while(cnt){
			if(a.size()==n){
				b+=p[cnt--];
			}else{
				if(!flag){
					if(p[cnt]>p[cnt-1]){
						flag=1;
					}
					a+=p[cnt--],b+=p[cnt--];
				}else{
					b+=p[cnt--],a+=p[cnt--];
				}
			}
		}
		reverse(a.begin(),a.end());
		reverse(b.begin(),b.end());
		a=" "+a,b=" "+b;
//		for(int i=1;i<=n;i++){
//			cout<<(int)a[i];
//		}
//		cout<<' ';
//		for(int i=1;i<=m;i++){
//			cout<<(int)b[i];
//		}
//		cout<<'\n';
		for(int i=0;i<=n+m;i++){
			c[i]=0;
		}
		c[0]=n+m;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				c[i+j-1]+=a[i]*b[j];
			}
		}
		for(int i=1;i<=c[0];i++){
			c[i+1]+=c[i]/10,c[i]%=10;
		}
		while(c[0]>1&&!c[c[0]]){
			c[0]--;
		}
		for(int i=c[0];i;i--){
			cout<<c[i];
		}
		cout<<'\n';
	}
	return 0;
}
}
signed main(){return asbt::main();}

B. 游戏(game)

考虑如果确定了 \(A\)\(B\) 第一次选的位置,那么 \(B\) 后面的每一步都可以与 \(A\) 相向而行。进而当确定了 \(A\) 的起点 \(x\) 后,\(B\) 可以通过选择起点来使 \(A\) 最后的区间之和为所有包含了 \(x\) 的长为 \(\lceil\frac{n}{2}\rceil\) 的区间中最小的一个。而 \(A\) 又可以选择起点,因此用 ST 表求出每个 \(x\) 对应的最小的和,再对每个 \(x\) 的答案取 \(\max\) 即可。

image

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
ifstream cin("game.in");
ofstream cout("game.out");
const int maxn=5e5+5,inf=2e9;
int n,a[maxn<<1],st[maxn][22];
il int query(int l,int r){
	if(l>r){
		return inf;
	}
	int p=__lg(r-l+1);
	return min(st[l][p],st[r-(1<<p)+1][p]);
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[i+n]=a[i];
	}
	for(int i=1;i<=n<<1;i++){
		a[i]+=a[i-1];
	}
	int B=(n+1)/2;
	for(int i=1;i<=n;i++){
		st[i][0]=i<B?a[i+n]-a[i+n-B]:a[i]-a[i-B];
	}
	for(int j=1;j<=19;j++){
		for(int i=1;i+(1<<j)-1<=n;i++){
			st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		int l=i,r=i+B-1;
		if(r>n){
			r-=n;
		}
		ans=max(ans,l<=r?query(l,r):min(query(1,r),query(l,n)));
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

C. 海报(posters)

D. 环(ring)

根号分治。设阈值 \(B\),对于块长小于 \(B\) 的环,考虑直接用数据结构维护其贡献。每次修改操作要进行 \(B\) 次单点修改,分块即可,修改 \(O(B)\),查询 \(O(B+\frac{n}{B})\)

对于较大的环,因为其数量最多有 \(\frac{n}{B}\) 个,一个直接的想法是维护环上的前缀和,二分出查询区间在环上的位置,单次复杂度 \(O(\frac{n}{B}\log n)\)。考虑离线,对于每个环算出每个位置的前驱后继,修改过程中记录偏移量,这样对于每个询问就可以 \(O(1)\) 算出这个大环的贡献,总时间复杂度 \(O((n+q)\frac{n}{B})\)

\(B=\sqrt{n}\),总时间复杂度 \(O((n+q)\sqrt{n})\)

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
ifstream cin("ring.in");
ofstream cout("ring.out");
const int maxn=1.5e5+5,B=387,maxb=397;
int n,m,q,a[maxn],b[maxn],c[maxn],ans[maxn];
int bnm,st[maxb],ed[maxb],sm[maxb],bel[maxn];
int sum[maxn<<1],pre[maxn],nxt[maxn];
vector<int> vc[maxn];
struct{
	int opt,l,r,x;
}d[maxn];
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++){
		cin>>b[i];
		c[b[i]]++,vc[b[i]].pb(i);
	}
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	bnm=n/B;
	for(int i=1;i<=bnm;i++){
		st[i]=ed[i-1]+1,ed[i]=ed[i-1]+B;
		for(int j=st[i];j<=ed[i];j++){
			bel[j]=i;
			if(c[b[j]]<=B){
				sm[i]+=a[j];
			}
		}
	}
	if(ed[bnm]<n){
		st[bnm+1]=ed[bnm]+1,ed[bnm+1]=n;
		bnm++;
		for(int i=st[bnm];i<=ed[bnm];i++){
			bel[i]=bnm;
			if(c[b[i]]<=B){
				sm[bnm]+=a[i];
			}
		}
	}
//	for(int i=1;i<=n;i++){
//		cout<<bel[i]<<' ';
//	}
//	cout<<'\n';
//	for(int i=1;i<=bnm;i++){
//		cout<<st[i]<<' '<<ed[i]<<'\n';
//	}
	for(int i=1;i<=q;i++){
		int opt;
		cin>>opt;
		if(opt==1){
			int l,r;
			cin>>l>>r;
			d[i]={opt,l,r,0};
			int pl=bel[l],pr=bel[r];
			if(pl==pr){
				for(int j=l;j<=r;j++){
					if(c[b[j]]<=B){
						ans[i]+=a[j];
					}
				}
			}else{
				for(int j=pl+1;j<pr;j++){
//					cout<<"b "<<j<<' '<<sm[j]<<'\n';
					ans[i]+=sm[j];
				}
				for(int j=l;j<=ed[pl];j++){
					if(c[b[j]]<=B){
//						cout<<"a "<<j<<' '<<a[j]<<'\n';
						ans[i]+=a[j];
					}
				}
				for(int j=st[pr];j<=r;j++){
					if(c[b[j]]<=B){
//						cout<<"a "<<j<<' '<<a[j]<<'\n';
						ans[i]+=a[j];
					}
				}
			}
		}else{
			int x;
			cin>>x;
			d[i]={opt,0,0,x};
			if(c[x]<=B){
				for(int j=0;j<c[x];j++){
					sm[bel[vc[x][j]]]+=a[vc[x][j?j-1:c[x]-1]]-a[vc[x][j]];
				}
				for(int j=c[x]-1;j;j--){
					swap(a[vc[x][j]],a[vc[x][j-1]]);
				}
			}
		}
	}
//	for(int i=1;i<=q;i++){
//		if(d[i].opt==1){
//			cout<<ans[i]<<'\n';
//		}
//	}
	for(int i=1;i<=m;i++){
		if(c[i]<=B){
			continue;
		}
//		cout<<i<<":\n";
		int cnt=0;
		for(int j=0;j<c[i];j++){
			sum[cnt+1]=sum[cnt]+a[vc[i][j]];
			cnt++;
		}
		for(int j=0;j<c[i];j++){
			sum[cnt+1]=sum[cnt]+a[vc[i][j]];
			cnt++;
		}
//		for(int j=0;j<=c[i]*2;j++){
//			cout<<sum[j]<<' ';
//		}
//		cout<<'\n';
		int p=1;
		for(int j=0;j<c[i];j++){
			while(p<=vc[i][j]){
				nxt[p++]=j+1;
			}
		}
		while(p<=n){
			nxt[p++]=c[i]+1;
		}
		p=n;
		for(int j=c[i]-1;~j;j--){
			while(p>=vc[i][j]){
				pre[p--]=j+1;
			}
		}
		while(p){
			pre[p--]=0;
		}
//		for(int i=1;i<=n;i++){
//			cout<<pre[i]<<' ';
//		}
//		cout<<'\n';
//		for(int i=1;i<=n;i++){
//			cout<<nxt[i]<<' ';
//		}
//		cout<<'\n';
		cnt=0;
		for(int j=1;j<=q;j++){
			if(d[j].opt==1){
				int l=d[j].l,r=d[j].r;
//				cout<<l<<' '<<r<<' ';
				l=nxt[l]+c[i],r=pre[r]+c[i];
				if(l>r){
					continue;
				}
				l-=cnt,r-=cnt;
//				cout<<l<<' '<<r<<' '<<sum[r]-sum[l-1]<<'\n';
				ans[j]+=sum[r]-sum[l-1];
			}else{
				if(d[j].x==i){
					cnt=(cnt+1)%c[i];
				}
			}
		}
	}
	for(int i=1;i<=q;i++){
		if(d[i].opt==1){
			cout<<ans[i]<<'\n';
		}
	}
	return 0;
}
}
signed main(){return asbt::main();}
posted @ 2025-10-29 15:57  zhangxy__hp  阅读(61)  评论(2)    收藏  举报