【比赛记录】2025暑假集训模拟赛合集Ⅲ

2025CSP-S模拟赛41

A B C D Sum Rank
100 27 40 8 175 10/19

A. 限速(speed)

如果最小生成树中最大的边权 \(\ge k\),那么只需在最小生成树上做修改即可,其它生成树不可能更优;否则用与 \(k\) 的差值最小的边替换掉一条即可。用二分实现 kruskal 的又是什么人类呢

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e5+5,inf=0x3f3f3f3f;
int n,m,kk,fa[maxn],sz[maxn];
struct edge{
	int u,v,w;
	edge(int u=0,int v=0,int w=0):u(u),v(v),w(w){}
	il bool operator<(const edge &x)const{
		return w<x.w;
	}
}e[maxn];
il int find(int x){
	return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il bool check(int x){
	for(int i=1;i<=n;i++){
		fa[i]=i,sz[i]=1;
	}
	for(int i=1;i<=m;i++){
		if(e[i].w>x){
			break;
		}
		int u=find(e[i].u),v=find(e[i].v);
		if(u!=v){
			fa[v]=u,sz[u]+=sz[v];
		}
	}
	return sz[find(1)]==n;
}
int main(){
	freopen("speed.out","w",stdout);
	freopen("speed.in","r",stdin);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>kk;
	for(int i=1,u,v,w;i<=m;i++){
		cin>>u>>v>>w;
		e[i]=edge(u,v,w);
	}
	sort(e+1,e+m+1);
	if(check(kk)){
		int ans=inf;
		for(int i=1;i<=m;i++){
			ans=min(ans,abs(e[i].w-kk));
		}
		cout<<ans;
		return 0;
	}
	int l=1,r=1e9;
	while(l<r){
		int mid=(l+r)>>1;
		if(check(mid)){
			r=mid;
		}
		else{
			l=mid+1;
		}
	}
	ll ans=0;
	for(int i=1;i<=n;i++){
		fa[i]=i;
	}
	for(int i=1;i<=m;i++){
		if(e[i].w>l){
			break;
		}
		int u=find(e[i].u),v=find(e[i].v),w=e[i].w;
		if(u!=v){
			fa[v]=u;
			ans+=max(w-kk,0);
		}
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

B. 酒鬼 (drunkard)

对于 \(p_i>1\),如果 \(p_i+q_i\not\equiv p_j+q_j\pmod{2}\) 那么产生矛盾。而对于 \(p_i=1\),如果与后面产生矛盾那么可以将最小出发时间变大为 \(q_i+1\) 来满足要求。同时对于相邻时间,\(|p_i-p_j|>q_j-q_i\) 那么不合法。用 map 维护,模拟即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define lwrb lower_bound
using namespace std;
namespace asbt{
const int inf=0x3f3f3f3f;
int n,m,mn,mx=inf;
bool flag;
map<int,pii> S;
il bool check(pii a,pii b){
	if(abs(a.fir-b.fir)>b.sec-a.sec){
		return 0;
	}
	if(a.fir>1&&a.sec<=mn){
		return 0;
	}
	if(b.fir>1){
		mx=min(mx,b.sec-b.fir+1);
	}
	#define chk ((a.fir+a.sec)%2==(b.fir+b.sec)%2)
	if(a.fir==1&&a.sec<=mx){
		if(!chk){
			mn=max(mn,a.sec+1);
		}
		return 1;
	}
	return chk;
	#undef chk
}
int main(){
	freopen("drunkard.in","r",stdin);
	freopen("drunkard.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	S[0]=mp(1,0);
	while(m--){
		string opt;
		cin>>opt;
		switch(opt[1]){
			case 'l':{
				int p,q;
				cin>>p>>q;
				if(flag){
					continue;
				}
				if(S.count(q)&&S[q].fir!=p){
					flag=1;
					continue;
				}
				auto t=S.lwrb(q);
				if(t!=S.end()&&!check(mp(p,q),t->sec)){
					flag=1;
					continue;
				}
				if(!check(prev(t)->sec,mp(p,q))){
					flag=1;
					continue;
				}
				S[q]=mp(p,q);
				break;
			}
			case 'i':{
				if(flag){
					cout<<"bad\n";
				}
				else{
					cout<<mn<<'\n';
				}
				break;
			}
			default:{
				if(flag){
					cout<<"bad\n";
				}
				else if(mx==inf){
					cout<<"inf\n";
				}
				else{
					cout<<mx<<'\n';
				}
				break;
			}
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

C. 距离(distance)

\(\min(|a_x-a_y|,|b_x-b_y|)=|a_x-a_y|+|b_x-b_y|-\max(|a_x-a_y|,|b_x-b_y|)\)。前面是曼哈顿距离,后面是切比雪夫距离,切比雪夫转曼哈顿后就转化为了形如 \(\sum\limits_{x\in\operatorname{subtree}(u)}\sum\limits_{y\in\operatorname{subtree}(u)}|a_x-a_y|\) 的子问题。考虑权值线段树,在每个节点维护答案、数量和总和即可线性对数求解。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define ls(id) tr[id].ls
#define rs(id) tr[id].rs
#define cnt(id) tr[id].cnt
#define sum(id) tr[id].sum
#define ans(id) tr[id].ans
using namespace std;
namespace asbt{
const int maxn=5e5+5,mod=1e9+7;
il int pls(int x,int y){
	return x+y<mod?x+y:x+y-mod;
}
il int sub(int x,int y){
	return x<y?x-y+mod:x-y;
}
il void add(int &x,int y){
	x=pls(x,y);
}
il void mns(int &x,int y){
	x=sub(x,y);
}
int n,tot,rt[maxn],ans[maxn];
ll a[maxn],b[maxn],c[maxn],d[maxn];
vector<int> e[maxn];
struct{
	int ls,rs,cnt,sum,ans;
}tr[maxn*39];
il void pushup(int id){
	cnt(id)=cnt(ls(id))+cnt(rs(id));
	sum(id)=pls(sum(ls(id)),sum(rs(id)));
//	cout<<cnt(ls(id))<<' '<<sum(ls(id))<<' '<<cnt(rs(id))<<' '<<sum(rs(id))<<'\n';
	ans(id)=((ans(ls(id))+ans(rs(id))+cnt(ls(id))*1ll*sum(rs(id))-cnt(rs(id))*1ll*sum(ls(id)))%mod+mod)%mod;
}
il void insert(int &id,ll l,ll r,ll p){
	if(!id){
		id=++tot;
		ls(id)=rs(id)=cnt(id)=sum(id)=ans(id)=0;
	}
	if(l==r){
//		cout<<sum(id)<<' '<<p<<'\n';
		cnt(id)++,sum(id)=((sum(id)+p)%mod+mod)%mod;
//		cout<<ans(id)<<' ';
		return ;
	}
	ll mid=(l+r)>>1;
	if(p<=mid){
		insert(ls(id),l,mid,p);
	}
	else{
		insert(rs(id),mid+1,r,p);
	}
	pushup(id);
}
il int merge(int p,int q,ll l,ll r){
	if(!p||!q){
		return p+q;
	}
	if(l==r){
		cnt(p)+=cnt(q),add(sum(p),sum(q));
//		cout<<ans(p)<<' '<<ans(q)<<' ';
		return p;
	}
	ll mid=(l+r)>>1;
	ls(p)=merge(ls(p),ls(q),l,mid);
	rs(p)=merge(rs(p),rs(q),mid+1,r);
	pushup(p);
	return p;
}
il void dfs(int u,int fa,ll *a,bool flag){
	insert(rt[u],-2e9,2e9,a[u]);
	for(int v:e[u]){
		if(v==fa){
			continue;
		}
		dfs(v,u,a,flag);
		rt[u]=merge(rt[u],rt[v],-2e9,2e9);
	}
	(flag?add:mns)(ans[u],ans(rt[u]));
}
il void init(){
	tot=0;
	for(int i=1;i<=n;i++){
		rt[i]=0;
	}
}
int main(){
//	freopen("C.in","r",stdin);
	freopen("distance.in","r",stdin);
	freopen("distance.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].pb(v),e[v].pb(u);
	}
	for(int i=1;i<=n;i++){
		cin>>a[i]>>b[i];
		a[i]<<=1,b[i]<<=1;
		c[i]=(a[i]+b[i])>>1,d[i]=(a[i]-b[i])>>1;
//		cout<<a[i]<<' '<<b[i]<<' '<<c[i]<<' '<<d[i]<<'\n';
	}
	init(),dfs(1,0,a,1);
	init(),dfs(1,0,b,1);
	init(),dfs(1,0,c,0);
	init(),dfs(1,0,d,0);
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

D. 团队选拔(selection)

2025CSP-S模拟赛42

A B C D Sum Rank
100 40 - 0 140 13/20

A. 追逐游戏 (chase)

考虑小 Y 的策略,一定要先走到 \((S,T)\) 的路径上,然后再和小 Z 相向/同向走。于是我们只需要先找到 \(S'\)\((S,T)\) 上最近的点,然后再判断二者的位置即可。时间复杂度线性对数。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=2e5+5;
int n,m,dep[maxn],dfn[maxn],cnt;
int oula[maxn<<1],idx[maxn<<1];
int anc[maxn][20];
vector<int> e[maxn];
il void dfs(int u,int fa){
	dep[u]=dep[fa]+1;
	dfn[u]=++cnt,oula[cnt]=cnt,idx[cnt]=u;
	anc[u][0]=fa;
	for(int i=1;i<=18;i++){
		anc[u][i]=anc[anc[u][i-1]][i-1];
	}
	for(int v:e[u]){
		if(v==fa){
			continue;
		}
		dfs(v,u);
		oula[++cnt]=dfn[u];
	}
}
struct{
	int Log[maxn<<1],st[maxn<<1][20];
	il void build(){
		for(int i=2;i<=cnt;i++){
			Log[i]=Log[i>>1]+1;
		}
		for(int i=1;i<=cnt;i++){
			st[i][0]=oula[i];
		}
		for(int j=1;j<=Log[cnt];j++){
			for(int i=1;i+(1<<j)-1<=cnt;i++){
				st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
			}
		}
	}
	il int query(int l,int r){
		int p=Log[r-l+1];
		return min(st[l][p],st[r-(1<<p)+1][p]);
	}
}ST;
il int lca(int u,int v){
	if(dfn[u]>dfn[v]){
		swap(u,v);
	}
	return idx[ST.query(dfn[u],dfn[v])];
}
il int dis(int u,int v){
	return dep[u]+dep[v]-dep[lca(u,v)]*2;
}
il int kth(int u,int k){
	int t=0;
	while(k){
		if(k&1){
			u=anc[u][t];
		}
		k>>=1,t++;
	}
	return u;
}
il int kth(int u,int v,int k){
	int x=lca(u,v);
	if(k<=dis(u,x)){
		return kth(u,k);
	}
	return kth(v,dis(u,v)-k);
}
int main(){
//	system("fc ex_chase2.out my.out");
	freopen("chase.in","r",stdin);
	freopen("chase.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].pb(v),e[v].pb(u);
	}
	dfs(1,0),ST.build();
	while(m--){
		int x,y,z;
		cin>>x>>y>>z;
		int t=lca(x,z),_t=lca(y,z);
		if(dep[t]<dep[_t]){
			t=_t;
		}
		_t=lca(x,y);
		if(dep[t]<dep[_t]){
			t=_t;
		}
		int dx1=dis(x,t),dz1=dis(z,t);
		if(dx1<dz1){
			cout<<dz1+dis(t,y)<<' '<<y<<'\n';
			continue;
		}
		x=kth(x,y,dz1);
		int dt1=dis(x,t);
		if(dt1&1){
			cout<<dz1+dt1/2+1<<' '<<kth(t,x,dt1>>1)<<'\n';
		}
		else{
			cout<<dz1+dt1/2<<' '<<kth(x,t,dt1>>1)<<'\n';
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

B. 统计

考虑维护 \(V_{i,j}\) 表示 \([1,i]\)\(j\) 出现了多少次,于是 \([l,r]\) 合法等价于 \(\forall j\in[1,m-1],V_{r,j}-V_{l-1,j}=V_{r,j+1}-V_{l-1,j+1}\Leftrightarrow\forall j\in[1,m-1],V_{r,j}-V_{r,j+1}=V_{l-1,j}-V_{l-1,j+1}\)。于是我们维护 \(V\) 的差分数组的哈希值,再用哈希表存一下即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ull unsigned ll
using namespace std;
namespace asbt{
const int maxn=1e6+49,N=1e6,M=1e6+39;
ull bas=2e6+39;
int T,n,m,hd[maxn],enm;
ll ans;
ull pw[maxn];
struct{
	ull val;
	int sz,nxt;
}e[maxn];
il void insert(ull x){
	int y=x%M;
	for(int i=hd[y];i;i=e[i].nxt){
		if(e[i].val==x){
			ans+=e[i].sz++;
			return ;
		}
	}
	e[++enm]={x,1,hd[y]},hd[y]=enm;
}
int main(){
	freopen("st.in","r",stdin);
	freopen("st.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	pw[0]=1;
	for(int i=1;i<=N;i++){
		pw[i]=pw[i-1]*bas;
	}
	cin>>T;
	while(T--){
		cin>>n>>m,enm=ans=0;
		for(int i=0;i<M;i++){
			hd[i]=0;
		}
		ull ha=0;
		for(int i=1;i<m;i++){
			ha+=N*pw[i];
		}
		insert(ha);
		for(int i=1,x;i<=n;i++){
			cin>>x;
			if(x>1){
				ha-=pw[x-1];
			}
			if(x<m){
				ha+=pw[x];
			}
			insert(ha);
		}
		cout<<ans<<'\n';
	}
	return 0;
}
}
signed main(){return asbt::main();}

C. 软件工程

按是否有某个集合的交集为空分类讨论。如果有,那么除了这个集合外的集合中一定都只有一条线段,直接取最长的 \(k-1\) 条线段即可。如果没有,首先如果 \(i\) 完全覆盖了 \(j\),那么 \(i\) 要么单独成一个集合要么和 \(j\) 放在一个集合,不会有其他可能。于是先将所有 \(i\) 去掉,剩下的线段 \(l\)\(r\) 一定都是单增的,可以 DP 求出最大贡献,最后再在去掉的线段中取剩下的集合即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e3+5;
int n,m,c1,c2;
ll f[maxn][maxn],g[maxn][maxn];
struct node{
	int l,r;
}a[maxn],b[maxn],c[maxn];
il bool cmp1(node x,node y){
	return x.r-x.l>y.r-y.l;
}
il bool cmp2(node x,node y){
	return x.l<y.l||x.l==y.l&&x.r>y.r;
}
il ll solve1(){
	sort(a+1,a+n+1,cmp1);
	ll ans=0;
	for(int i=1;i<m;i++){
		ans+=a[i].r-a[i].l;
	}
	return ans;
}
il ll solve2(){
	sort(a+1,a+n+1,cmp2);
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			if(a[i].l<=a[j].l&&a[i].r>=a[j].r){
				c[++c2]=a[i];
				goto togo;
			}
		}
		b[++c1]=a[i];
		togo:;
	}
//	cout<<c1<<'\n';
//	for(int i=1;i<=c1;i++){
//		cout<<b[i].l<<' '<<b[i].r<<'\n';
//	}
	for(int i=0;i<=c1;i++){
		g[0][i]=b[1].r;
	}
	for(int i=1;i<=m;i++){
		for(int j=i;j<=c1;j++){
			f[i][j]=g[i-1][j-1]-b[j].l;
			g[i][j]=max(g[i][j-1],f[i][j]+b[j+1].r);
		}
	}
//	for(int i=0;i<=m;i++){
//		for(int j=1;j<=c1;j++){
//			cout<<f[i][j]<<' ';
//		}
//		cout<<'\n';
//	}
	sort(c+1,c+c2+1,cmp1);
//	cout<<c2<<'\n';
//	for(int i=1;i<=c2;i++){
//		cout<<c[i].l<<' '<<c[i].r<<'\n';
//	}
	ll ans=0;
	for(int i=1;i<=min(m,c1);i++){
		ll res=f[i][c1];
//		cout<<res<<'\n';
		for(int j=1;j<=min(c2,m-i);j++){
			res+=c[j].r-c[j].l;
		}
		ans=max(ans,res);
	}
	return ans;
}
int main(){
	freopen("se.in","r",stdin);
	freopen("se.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i].l>>a[i].r;
	}
//	cout<<solve1()<<' '<<solve2()<<'\n';
	cout<<max(solve1(),solve2());
	return 0;
}
}
int main(){return asbt::main();}

D. 命运的X

/

答案为 \(\sum\limits_{i\in[1,n]\land B[1,i]=B[n-i+1,n]}m^i\)。洛谷题解区有一个巧妙而有趣的证明,就不抄了。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e5+5,mod=998244353;
int T,m,n,a[maxn],nxt[maxn];
il int qpow(int x,int y){
	int res=1;
	while(y){
		if(y&1){
			res=res*1ll*x%mod;
		}
		x=x*1ll*x%mod,y>>=1;
	}
	return res;
}
int main(){
	freopen("x.in","r",stdin);
	freopen("x.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>m>>n;
		for(int i=1,j=0;i<=n;i++){
			cin>>a[i];
			if(i==1){
				continue;
			}
			while(j&&a[i]!=a[j+1]){
				j=nxt[j];
			}
			if(a[i]==a[j+1]){
				j++;
			}
			nxt[i]=j;
		}
		int ans=0;
		for(int i=n;i;i=nxt[i]){
			(ans+=qpow(m,i))%=mod;
		}
		cout<<ans<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

2025CSP-S模拟赛43

A B C D Sum Rank
100 10 - 20 130 8/19

A. 四舍五入

题目要求 \(i\bmod j<\frac{j}{2}\),我们考虑 \(j\)\(i\) 的大小关系。如果 \(i<j\) 那么 \(i\bmod j=i\)\(j>2i\)。否则 \(j\le i\),此时从 \(i\) 的角度计算 \(j\) 的数量是困难的,考虑计算每个 \(j\) 的贡献。对于 \(k\in\mathbb{N}^*\)\(i\in[kj,kj+\lfloor\frac{j}{2}\rfloor]\) 都会被 \(j\) 贡献,差分即可。时间复杂度调和级数。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e6+5;
int n,f[maxn];
int main(){
	freopen("count.in","r",stdin);
	freopen("count.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j+=i){
			f[j]++,f[min(j+(i+1)/2,n+1)]--;
		}
	}
	for(int i=1;i<=n;i++){
		f[i]+=f[i-1];
		cout<<f[i]+n-min(i<<1,n)<<' ';
	}
	return 0;
}
}
int main(){return asbt::main();}

B. 填算符

首先可以发现,将 \(k\)& 都放在前面得到的答案一定是最优的,因为先或后与有可能会浪费,而如果先与后或都无法得到 \(1\),先或后与更不可能得到 \(1\)。然后考虑将每个 & 往后移,对于第 \(i\) 个位置,如果将目前最后一个 & 移到这里不影响答案,那么就移过来。用 ST 表预处理区间与和区间或即可。时间复杂度线性对数。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e6+5;
int n,m,Log[maxn],stk[maxn],top;
ll a[maxn];
struct btand{
	il ll operator()(ll x,ll y){
		return x&y;
	}
};
struct btor{
	il ll operator()(ll x,ll y){
		return x|y;
	}
};
template<typename Tp>struct STable{
	Tp opt;
	ll st[maxn][22];
	il void build(){
		for(int i=1;i<=n;i++){
			st[i][0]=a[i];
		}
		for(int j=1;j<=Log[n];j++){
			for(int i=1;i+(1<<j)-1<=n;i++){
				st[i][j]=opt(st[i][j-1],st[i+(1<<(j-1))][j-1]);
			}
		}
	}
	il ll query(int l,int r){
		int p=Log[r-l+1];
		return opt(st[l][p],st[r-(1<<p)+1][p]);
	}
};
STable<btand> STand;
STable<btor> STor;
int main(){
	freopen("bitop.in","r",stdin);
	freopen("bitop.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=2;i<=n;i++){
		Log[i]=Log[i>>1]+1;
	}
	STand.build(),STor.build();
	ll ans=a[1];
	for(int i=2;i<=m+1;i++){
		ans&=a[i];
	}
	for(int i=m+2;i<=n;i++){
		ans|=a[i];
	}
	int j=m;
	for(int i=n-1;i;i--){
		if(!j||i<=j){
			break;
		}
		if(((STand.query(1,j)|STor.query(j+1,i))&a[i+1]&ans)==ans){
			stk[++top]=i,j--;
		}
		else{
			ans^=a[i+1]&ans;
		}
	}
	while(j){
		stk[++top]=j--;
	}
	while(top){
		cout<<stk[top--]<<' ';
	}
	return 0;
}
}
int main(){return asbt::main();}

C. 道路修建

D. 逆序图

首先有一个性质:连通块是值域连续的一段区间。原因是由于单调性,两个连通块不可能相互嵌套。

考虑 DP。设:

  • \(f_{i,0}\) 表示 \(1\)\(i\) 的排列形成了很多个连通块的总权值和。

  • \(f_{i,1}\) 表示 \(1\)\(i\) 的排列形成了一个连通块的总权值和。

  • \(g_i\) 表示 \(1\)\(i\) 的排列形成了一个连通块的总方案数。

题目求 \(f(P_i-P_j)\),只和差值有关,于是可以进行转移。

枚举每对数的贡献,有:

\[f_{i,0}=\sum_{j=1}^{i-1}f(j)(i-j){i\choose 2}(i-2)! \]

容斥可得:

\[g_i=i!-\sum_{j=1}^{i-1}j!g_{i-j}\\ f_{i,1}=f_{i,0}-\sum_{j=1}^{i-1}j!f_{i-j,1}+f_{j,0}g_{i,j} \]

于是对于 \(ans_i\) 表示 \(1\)\(i\) 的排列的答案,考虑将这一块加入末尾,或跳过这一块,或以这一块为开头,有:

\[ans_i=\sum_{j=0}^{i-1}ans_j\times f_{i-j,1}+ans_j\times g_{i-j}+j!\times f_{i-j,1} \]

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

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e3+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 sub(int x,int y){
	return x<y?x-y+mod:x-y;
}
il void mns(int &x,int y){
	x=sub(x,y);
}
int n,a[maxn],fac[maxn],f[maxn][2],g[maxn],ans[maxn];
int main(){
	freopen("graph.in","r",stdin);
	freopen("graph.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	fac[0]=1;
	for(int i=1;i<=n;i++){
		fac[i]=fac[i-1]*1ll*i%mod;
	}
	for(int i=1;i<n;i++){
		cin>>a[i];
	}
	g[1]=1;
	for(int i=2;i<=n;i++){
		for(int j=1;j<i;j++){
			g[i]=(fac[j]*1ll*g[i-j]+g[i])%mod;
			f[i][0]=(a[j]*1ll*(i-j)+f[i][0])%mod;
		}
		g[i]=sub(fac[i],g[i]);
		f[i][0]=i*1ll*(i-1)/2%mod*fac[i-2]%mod*f[i][0]%mod;
		for(int j=1;j<i;j++){
			f[i][1]=(fac[j]*1ll*f[i-j][1]+f[j][0]*1ll*g[i-j]+f[i][1])%mod;
		}
		f[i][1]=sub(f[i][0],f[i][1]);
		for(int j=0;j<i;j++){
			ans[i]=(ans[j]*1ll*(f[i-j][1]+g[i-j])+fac[j]*1ll*f[i-j][1]+ans[i])%mod;
		}
	}
	cout<<ans[n];
	return 0;
}
}
int main(){return asbt::main();}

2025CSP-S模拟赛44

A B C D Sum Rank
100 - 94 10 204 9/16

T2 大水题没看出来就会个 T1 还假了,我真服了

A. 选彩笔(rgb)

考虑二分,对于每个 \(i\),给 \((R_i,G_i,B_i)\) 的权值加一,于是我们要选的相当于是一个边长为 \(mid\) 的立方体,枚举顶点,三维前缀和即可。注意 \((R_i,G_i,B_i)\) 不一定是立方体的顶点,否则就会被这组数据卡掉:

12 12
0 1 0
1 0 0
2 1 0
1 2 0
0 1 2
1 0 2
2 1 2
1 2 2
0 0 1
0 2 1
2 0 1
2 2 1

别问我怎么知道的

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,m,a[maxn],b[maxn],c[maxn],cnt[305][305][305];
il bool check(int kk){
	for(int x1=1,x2=kk+1;x2<=300;x1++,x2++){
		for(int y1=1,y2=kk+1;y2<=300;y1++,y2++){
			for(int z1=1,z2=kk+1;z2<=300;z1++,z2++){
				if(cnt[x2][y2][z2]-cnt[x1-1][y2][z2]-cnt[x2][y1-1][z2]-cnt[x2][y2][z1-1]+cnt[x1-1][y1-1][z2]+cnt[x1-1][y2][z1-1]+cnt[x2][y1-1][z1-1]-cnt[x1-1][y1-1][z1-1]>=m){
					return 1;
				}
			}
		}
	}
	return 0;
}
int main(){
	freopen("rgb.in","r",stdin);
	freopen("rgb.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i]>>b[i]>>c[i];
		cnt[++a[i]][++b[i]][++c[i]]++;
	}
	for(int i=1;i<=300;i++){
		for(int j=1;j<=300;j++){
			for(int k=1;k<=300;k++){
				cnt[i][j][k]+=cnt[i-1][j][k]+cnt[i][j-1][k]+cnt[i][j][k-1]-cnt[i-1][j-1][k]-cnt[i-1][j][k-1]-cnt[i][j-1][k-1]+cnt[i-1][j-1][k-1];
			}
		}
	}
//	for(int i=1;i<=10;i++){
//		for(int j=1;j<=10;j++){
//			for(int k=1;k<=10;k++){
//				cout<<i<<' '<<j<<' '<<k<<' '<<cnt[i][j][k]<<'\n';
//			}
//		}
//	}
	int l=0,r=300;
	while(l<r){
		int mid=(l+r)>>1;
		if(check(mid)){
			r=mid;
		}
		else{
			l=mid+1;
		}
	}
	cout<<l;
	return 0;
}
}
int main(){return asbt::main();}

B. 兵蚁排序(sort)

首先发现每个位置最多能往前移动的距离等于它前面比它大的数的数量。依次考虑 \(b\) 的每一位 \(i\),在 \(a\) 中找到第一个等于 \(b_i\) 的位置 \(j\),然后将 \(j\) 一格一格地往前移即可,如果移不了就无解。显然操作次数小于 \(n^2\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
using namespace std;
namespace asbt{
const int maxn=1e3+5;
int T,n,a[maxn],b[maxn];
vector<pii> ans;
int main(){
	freopen("sort.in","r",stdin);
	freopen("sort.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>a[i];
		}
		for(int i=1;i<=n;i++){
			cin>>b[i];
		}
		ans.clear();
		for(int i=1;i<=n;i++){
			if(a[i]==b[i]){
				continue;
			}
			int j=i+1;
			for(;j<=n;j++){
				if(a[j]==b[i]){
					break;
				}
			}
			while(j>i){
				if(a[j]>a[j-1]){
					cout<<-1<<'\n';
					goto togo;
				}
				ans.pb(mp(j-1,j));
				swap(a[j-1],a[j]),j--;
			}
		}
		cout<<0<<'\n'<<ans.size()<<'\n';
		for(pii i:ans){
			cout<<i.fir<<' '<<i.sec<<'\n';
		}
		togo:;
	}
	return 0;
}
}
int main(){return asbt::main();}

C. 人口局 DBA(dba)

考虑枚举这个比 \(n\) 小的数前面有多少位与 \(n\) 相同,设后面还剩 \(a\) 位且和为 \(s\),于是我们需要求 \(\sum_{j=1}^{a}x_j=s,x_j\in[0,m-1],x_1<p\) 的解有多少组。

首先不考虑 \(x_1<p\) 这个限制。考虑二项式反演,令 \(h_i\) 表示恰好有 \(i\) 个数 \(\ge m\) 的方案数,\(g_k\) 表示钦定 \(i\) 个数 \(\ge m\) 的方案数,于是有:

\[g_k=\sum_{i=k}^{a}{i\choose k}h_i\Leftrightarrow h_i=\sum_{k=i}^{a}(-1)^{k-i}{k\choose i}g_k \]

而根据插板法,我们又可以算出 \(g_k={a\choose k}{s-km+a-1\choose a-1}\),于是有:

\[f_{a,s}=h_0=\sum_{k=0}^{a}(-1)^k{a\choose k}{s-km+a-1\choose a-1} \]

再考虑到 \(x_1<p\),答案即为:

\[\begin{aligned} &\sum_{i=0}^{p-1}f_{a-1,s-i}\\ =&\sum_{i=0}^{p-1}\sum_{k=0}^{a-1}(-1)^k{a-1\choose k}{s-i-km+a-2\choose a-2}\\ =&\sum_{k=0}^{a-1}(-1)^k{a-1\choose k}\sum_{i=0}^{p-1}{s-i-km+a-2\choose a-2}\\ =&\sum_{k=0}^{a-1}(-1)^k{a-1\choose k}({s-km+a-1\choose a-1}-{s-p-km+a-1\choose a-1}) \end{aligned} \]

时间复杂度 \(O(L^2)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e3+5,maxm=4e6+2e3+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 int sub(int x,int y){return x<y?x-y+mod:x-y;}
il void add(int &x,int y){x=pls(x,y);}
il void mns(int &x,int y){x=sub(x,y);}
int m,L,aa[maxn],fac[maxm],inv[maxm];
il void init(int n=4e6+2e3){
	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){
	if(x<y||y<0){
		return 0;
	}
	return fac[x]*1ll*inv[y]%mod*inv[x-y]%mod;
}
int main(){
	freopen("dba.in","r",stdin);
	freopen("dba.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>m>>L;
	for(int i=1;i<=L;i++){
		cin>>aa[i];
	}
	init();
	int s=aa[L],ans=0;
	for(int i=L-1,a=2,p;i;i--,a++){
		s+=aa[i],p=aa[i];
		for(int k=0;k<a;k++){
			(k&1?mns:add)(ans,C(a-1,k)*1ll*(C(s-k*m+a-1,a-1)-C(s-p-k*m+a-1,a-1)+mod)%mod);
		}
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

D. 银行的源起(banking)

posted @ 2025-08-25 20:48  zhangxy__hp  阅读(31)  评论(0)    收藏  举报