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

2025CSP-S模拟赛29

A B C D Sum Rank
50 50 30 0 130 10/17

A. 二分图匹配

首先考虑一个朴素的 DP:设 \(f_{i,j}\) 表示 \(S_1[1\dots i]\)\(S_2[1\dots j]\) 的最大匹配数量。发现空间开不下。注意到 \(f\) 数组的值最大为 \(\min(n,m)\),考虑交换下标与值域,设 \(f_{i,j}\) 表示 \(S_1[1\dots i]\) 匹配了 \(j\) 位在 \(S_2\) 上的最短前缀。记 \(g_{i,j}\) 表示 \(S_2\) 中第 \(i\) 位之后第一次出现字符 \(j\) 的位置,于是有转移:\(f_{i,j}=\min(f_{i-1,j},g_{f_{i-1,j-1},{S_1}_i})\)。时间复杂度 \(O(26m+n^2)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e3+5,maxm=1e6+5,inf=1e9;
int n,m,f[maxn][maxn],g[maxm][30],pos[30];
string s1,s2;
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>s1>>s2;
	s1=" "+s1,s2="A"+s2;
	memset(pos,0x3f,sizeof(pos));
	for(int i=m;~i;i--){
		for(int j=0;j<=25;j++){
			g[i][j]=pos[j];
		}
		pos[s2[i]-'A']=i;
	}
	memset(f,0x3f,sizeof(f));
	for(int i=0;i<=n;i++){
		f[i][0]=0;
		for(int j=1;j<=i;j++){
			f[i][j]=min(f[i-1][j],f[i-1][j-1]>=inf?inf:g[f[i-1][j-1]][s1[i]-'A']);
		}
	}
	for(int i=n;~i;i--){
		if(f[n][i]<=m){
			cout<<i;
			break;
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

B. 虚图

考虑直接对这些关键点二进制分组,当前位为 \(1\) 的连一条 \(S\xrightarrow{0} a_i\),否则连一条 \(a_i\xrightarrow{0} T\),从 \(S\) 跑最短路,用 \(dis_T\) 更新答案即可。因为两个不同的点至少有一位不同,所以这样的分组方式遍历了所有组合。时间复杂度 \(O((n+m)\log^2n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=2e5+5;
const ll inf=1e18;
int n,m,kk,a[maxn];
ll dis[maxn];
bool vis[maxn];
vector<pii> e[maxn];
struct edge{
	int u,v,w;
}E[maxn];
priority_queue<pii> q;
il void dijkstra(int u){
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[u]=0,q.push(mp(0,u));
	while(q.size()){
		int x=q.top().sec;
		q.pop();
		if(vis[x]){
			continue;
		}
		vis[x]=1;
		for(pii i:e[x]){
			int v=i.fir,w=i.sec;
			if(!vis[v]&&dis[v]>dis[x]+w){
				dis[v]=dis[x]+w;
				q.push(mp(-dis[v],v));
			}
		}
	}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>kk;
	for(int i=1;i<=m;i++){
		cin>>E[i].u>>E[i].v>>E[i].w;
	}
	for(int i=1;i<=kk;i++){
		cin>>a[i];
	}
	int S=n+1,T=n=S+1;
	ll ans=inf;
	for(int i=0;i<=17;i++){
		for(int j=1;j<=m;j++){
			e[E[j].u].pb(mp(E[j].v,E[j].w));
			e[E[j].v].pb(mp(E[j].u,E[j].w));
		}
		for(int j=1;j<=kk;j++){
			if(j>>i&1){
				e[S].pb(mp(a[j],0));
			}
			else{
				e[a[j]].pb(mp(T,0));
			}
		}
		dijkstra(S),ans=min(ans,dis[T]);
		for(int j=1;j<=n;j++){
			e[j].clear();
		}
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

C. 冒泡

考虑将答案差分,分为 \([1,r]\)\([1,l]\) 两部分。

对于 \([1,x]\),考虑枚举当前考虑的数有前端有多少与 \(x\) 相同。假设 \(x\)\(\overline{a_1a_2a_3\dots}\),如果当前枚举的为 \(\overline{a_1k}\;(k<a_2)\),那么后面的位数都可以随意乱选。将此时的答案拆成已确定的部分、未确定的部分和二者之间。设未确定的还有 \(len\) 位,对于已确定的部分,求出其逆序对数 \(tot\),这一部分答案为 \(tot\times10^{len}\);对于未确定的部分,任选两个位置组成逆序对,其他位置乱选,答案为 \(\frac{len\times(len-1)}{2}\times45\times10^{len-2}\);二者之间的部分,假设前面有一个 \(c\),若希望构成逆序对则后面一个位置的取值有 \(0\dots c-1\)\(c\) 种,贡献为 \(c\times len\times10^{len-1}\)

注意这样无法算入 \(x\) 的贡献,需要另外求出来。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e5+5,mod=1e9+7;
int T,pw[maxn];
string l,r;
il int calc(string s){
	int x=s.size(),tot=0,sum=0,res=0,cnt[15]={0};
	for(char ch:s){
		int c=ch^48;
		x--;
		for(int i=0;i<c;i++){
			if(x>=2){
				(res+=x*(x-1)/2%mod*45%mod*pw[x-2])%=mod;
			}
			if(x>=1){
				(res+=(sum+i)*x%mod*pw[x-1])%=mod;
			}
			(res+=(tot+cnt[i+1])*pw[x])%=mod;
		}
		(tot+=cnt[c+1])%=mod,(sum+=c)%=mod;
		for(int i=0;i<=c;i++){
			cnt[i]++;
		}
	}
	return res;
}
il int nxd(string s){
	int cnt[15]={0},res=0;
	for(char ch:s){
		int c=ch^48;
		for(int i=c+1;i<=9;i++){
			(res+=cnt[i])%=mod;
		}
		cnt[c]++;
	}
	return res;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	pw[0]=1;
	for(int i=1;i<=5e5;i++){
		pw[i]=pw[i-1]*10%mod;
	}
	cin>>T;
	while(T--){
		cin>>l>>r;
		cout<<(calc(r)-calc(l)+nxd(r)+mod)%mod<<"\n";
	}
	return 0;
}
}
signed main(){return asbt::main();}

D. 亲戚

\(a_i\) 表示包含了 \(x\) 的限制组成的集合,大小为 \(1\) 的限制不算。考虑组成了树之后,对于叶子节点 \(u\),一定有 \(a_u\subseteq a_{fa_u}\)。于是想到一个构造树的方式:每次选择一对点使得 \(a_u\subseteq a_v\)\(u\) 还未被加入至树中,将 \(u\) 加入至树中。此时将所有包含 \(u\) 的限制的大小减一,如果减成 \(1\) 了那么就删去这个限制。这样的复杂度为 \(O(n^3m)\)。显然 \(a\) 可以用 bitset 维护,优化至 \(O(\frac{n^3m}{w})\)。考虑将 \(u\) 加入树中,此时大小减成 \(1\) 的限制中剩下的那个点一定是 \(v\),于是可以用栈维护每个点被包含于哪些点,删去 \(u\) 时将 \(v\) 暴力重构。时间复杂度 \(O(\frac{n^2m}{w})\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e3+5;
int T,n,m,cnt[maxn];
int top[maxn],stk[maxn][maxn];
bool vis[maxn];
bitset<maxn> a[maxn];
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>m;
		for(int i=1;i<=n;i++){
			a[i].reset(),vis[i]=0;
		}
		for(int i=1,x;i<=m;i++){
			cin>>cnt[i];
			if(cnt[i]==1){
				cin>>x;
				continue;
			}
			for(int j=1;j<=cnt[i];j++){
				cin>>x;
				a[x].set(i);
			}
		}
		for(int i=1;i<=n;i++){
			top[i]=0;
			for(int j=1;j<=n;j++){
				if(i==j){
					continue;
				}
				if((a[i]|a[j])==a[j]){
					stk[i][++top[i]]=j;
				}
			}
		}
		for(int i=1;i<n;i++){
			for(int j=1;j<=n;j++){
				if(!vis[j]&&top[j]){
					int k=stk[j][top[j]--];
					while(top[j]&&vis[k]){
						k=stk[j][top[j]--];
					}
					if(vis[k]){
						continue;
					}
					for(int x=1;x<=m;x++){
						if(a[j][x]&&--cnt[x]==1){
							a[k].reset(x);
						}
					}
					top[k]=0;
					for(int x=1;x<=n;x++){
						if(x==k){
							continue;
						}
						if((a[k]|a[x])==a[x]){
							stk[k][++top[k]]=x;
						}
					}
					vis[j]=1;
					goto togo1;
				}
			}
			cout<<"NO\n";
			goto togo2;
			togo1:;
		}
		cout<<"YES\n";
		togo2:;
	}
	return 0;
}
}
int main(){return asbt::main();}
/*
8 8
4 1 5 4 6 
1 4 
3 2 1 6 
3 2 7 3 
4 2 5 7 1 
7 5 7 8 2 6 1 3 
7 3 8 5 6 4 7 1 
5 3 8 1 6 4 
*/

2025CSP-S模拟赛30

A B C D Sum Rank
10 50 - - 60 7/15

A. 天才俱乐部

\[\sum_{i=1}^{n}(a_i\bmod k)=s\\ \Leftrightarrow\sum_{i=1}^{n}(a_i-\lfloor\frac{a_i}{k}\rfloor\times k)=s\\ \Leftrightarrow k\times\sum_{i=1}^{n}\lfloor\frac{a_i}{k}\rfloor=\sum_{i=1}^{n}a_i-s\\ \Rightarrow k\mid \sum_{i=1}^{n}a_i-s \]

注意最后一步得出的只是必要条件,因此枚举 \(\sum_{i=1}^{n}a_i-s\) 的因子再一一验证即可。时间复杂度 \(O(tn\sqrt{\sum_{i=1}^{n}a_i-s})\)。注意特判 \(\sum_{i=1}^{n}a_i=s\) 的情况。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
int T,n,m,a[105];
il bool chk(int x){
	int t=0;
	for(int i=1;i<=n;i++){
		t+=a[i]%x;
	}
	return t==m;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>m;
		int sum=0;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			sum+=a[i];
		}
		if(sum==m){
			cout<<"YES\n";
			continue;
		}
		sum-=m;
		for(int i=1;i<=sum/i;i++){
			if(sum%i==0&&(chk(i)||chk(sum/i))){
				cout<<"YES\n";
				goto togo;
			}
		}
		cout<<"NO\n";
		togo:;
	}
	return 0;
}
}
int main(){return asbt::main();}

B. 实战教学

考虑二分答案 \(mid\),首先如果 \(\exist i,a_i+b_i>mid\) 那么一定不合法。于是对于每个配对 \((x,y)\),一定有 \(b_y\le mid-a_x\)。发现随着 \(a_x\) 的增大满足条件的 \(y\) 的数量不增,于是考虑从大到小枚举 \(a_x\),贪心地在所有满足条件的 \(y\) 中选择 \(a_y\) 最大的那一个。可以用线段树维护,时间复杂度线性对数方。

似乎可以用栈做到线性对数,但是 ds 代替思考还是太棒了😜

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,a[maxn],b[maxn],p[maxn],q[maxn],fp[maxn];
bool vis[maxn];
struct seg{
	int mxp,mxa,mxb;
	seg(int mxp=0,int mxa=0,int mxb=0):mxp(mxp),mxa(mxa),mxb(mxb){}
	il seg operator+(const seg &x)const{
		seg res;
		if(mxa>x.mxa){
			res=*this;
		}
		else{
			res=x;
		}
		res.mxb=max(mxb,x.mxb);
		return res;
	}
}tr[maxn<<2];
il void pushup(int id){
	tr[id]=tr[lid]+tr[rid];
}
il void build(int id,int l,int r){
	if(l==r){
		tr[id]=seg(p[l],a[p[l]],b[p[l]]);
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pushup(id);
}
il seg query(int id,int l,int r,int lim){
	if(l==r){
		return tr[id].mxb<=lim?tr[id]:seg();
	}
	int mid=(l+r)>>1;
	if(tr[lid].mxb<=lim){
		return tr[lid]+query(rid,mid+1,r,lim);
	}
	return query(lid,l,mid,lim);
}
il void upd(int id,int l,int r,int x){
	if(l==r){
		tr[id].mxa=0;
		return ;
	}
	int mid=(l+r)>>1;
	if(x<=mid){
		upd(lid,l,mid,x);
	}
	else{
		upd(rid,mid+1,r,x);
	}
	pushup(id);
}
il bool chk(int x){
	for(int i=1;i<=n;i++){
		if(a[i]+b[i]>x){
			return 0;
		}
		vis[i]=0;
	}
//	cout<<x<<"\n";
	build(1,1,n);
	for(int i=1;i<=n;i++){
		if(vis[q[i]]){
			continue;
		}
		vis[q[i]]=1;
		upd(1,1,n,fp[q[i]]);
		seg t=query(1,1,n,x-a[q[i]]);
		if(!t.mxa){
			return 0;
		}
//		cout<<q[i]<<" "<<t.mxp<<"\n";
		vis[t.mxp]=1;
		upd(1,1,n,fp[t.mxp]);
	}
	return 1;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	n<<=1;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		p[i]=q[i]=i;
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
	}
	sort(p+1,p+n+1,[](int x,int y){return b[x]<b[y];});
//	for(int i=1;i<=n;i++){
//		cout<<p[i]<<" ";
//	}
//	cout<<"\n";
	for(int i=1;i<=n;i++){
		fp[p[i]]=i;
	}
	sort(q+1,q+n+1,[](int x,int y){return a[x]>a[y];});
	ll l=1,r=2e9;
	while(l<r){
		int mid=(l+r)>>1;
		if(chk(mid)){
			r=mid;
		}
		else{
			l=mid+1;
		}
	}
	cout<<l;
	return 0;
}
}
int main(){return asbt::main();}

C. 穿越银匙之门

无根树非常难办,考虑先枚举根,将它变成有根树。那么根就不能动了。当然也有可能根也需要先操作一下,那就暴力枚举根的操作。

然后此时,我们发现一个点需要操作的充要条件为它在两棵树上的父亲不同。考虑判断是否有解,发现在 \(A\) 树中,儿子一定在父亲前面修改;在 \(B\) 树中,儿子一定在父亲后面修改。那么可以就此跑一个拓扑排序,来判断可行性。同时这也引出了一个判不合法的方式:在 \(A\) 树中,如果儿子不需要修改而父亲需要修改,那么无解。时间复杂度 \(O(tn^3)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int inf=1e9;
int T,n,af[55],bf[55],ad[55],q[55],gd[55];
bool f[55];
vector<int> A[55],B[55],G[55];
il void dfs(int u,int fa,vector<int>* e,int *p){
	p[u]=fa;
	for(int v:e[u]){
		if(v==fa){
			continue;
		}
		dfs(v,u,e,p);
	}
}
il int calc(){
	int cnt=0;
	for(int i=1;i<=n;i++){
		f[i]=af[i]!=bf[i];
		cnt+=f[i];
		G[i].clear(),gd[i]=0;
	}
	for(int i=1;i<=n;i++){
		if(!f[i]&&f[af[i]]){
			return inf;
		}
		if(f[i]){
			if(f[af[i]]){
				G[i].pb(af[i]),gd[af[i]]++;
			}
			if(f[bf[i]]){
				G[bf[i]].pb(i),gd[i]++;
			}
		}
	}
	int hd=1,tl=0;
	for(int i=1;i<=n;i++){
		if(f[i]&&!gd[i]){
			q[++tl]=i;
		}
	}
	while(hd<=tl){
		int u=q[hd++];
		for(int v:G[u]){
			if(--gd[v]==0){
				q[++tl]=v;
			}
		}
	}
	return tl==cnt?cnt:inf;
}
il void solve(){
	cin>>n;
	for(int i=1;i<=n;i++){
		ad[i]=0;
		A[i].clear(),B[i].clear();
	}
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		A[u].pb(v),A[v].pb(u);
		ad[u]++,ad[v]++;
	}
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		B[u].pb(v),B[v].pb(u);
	}
	dfs(1,0,A,af),dfs(1,0,B,bf);
	for(int i=1;i<=n;i++){
		if(af[i]!=bf[i]){
			goto togo;
		}
	}
	cout<<0<<"\n";
	return ;
	togo:;
	int ans=inf;
	for(int i=1;i<=n;i++){
		if(ad[i]>1){
			continue;
		}
		dfs(i,0,B,bf);
		for(int j=1;j<=n;j++){
			if(i==j){
				continue;
			}
			dfs(j,0,A,af);
			af[j]=i,af[i]=0;
			ans=min(ans,calc()+1);
		}
	}
	cout<<(ans==inf?-1:ans)<<"\n";
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		solve();
	}
	return 0;
}
}
int main(){return asbt::main();}

D. 绳网委托

考虑将为 \(0\) 的位置赋值为 \(1\)\(1\) 的位置赋值为 \(-1\),记前缀和为 \(pre\)\(1\) 的个数为 \(cnt\),LIS 中最后一个 \(0\) 在原序列中的位置为 \(x\),那么长度为 \(cnt+pre_x\)。而本题还有翻转操作,可以注意到翻转之后的前缀和就是原序列的一段前缀再加上 \(k\) 段不交的子串。

经过这个转化后,和这道题就非常类似了。考虑一个反悔贪心:每次选出最大的子串并加入答案,同时将这个子串中的数取相反数。这样如果后面选到的子串和这次的子串有重合,就相当于是将这次的子串反悔掉了一部分。线段树维护即可。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define lid id<<1
#define rid id<<1|1
#define fir first
#define sec second
using namespace std;
namespace asbt{
const int maxn=2e5+5;
int n,a[maxn],b[maxn],ans[maxn];
bool tag[maxn<<2];
struct node{
	int l,r,sum;
	node(int l=0,int r=0,int sum=0):l(l),r(r),sum(sum){}
	il node operator+(const node &x)const{
		return sum>x.sum?*this:x;
	}
};
struct seg{
	int sum,lm,rm,mm,pl,pr,ml,mr;
	seg(int sum=0,int lm=0,int rm=0,int mm=0,int pl=0,int pr=0,int ml=0,int mr=0)
		:sum(sum),lm(lm),rm(rm),mm(mm),pl(pl),pr(pr),ml(ml),mr(mr){}
	il seg operator+(const seg &x)const{
		seg res;
		res.sum=sum+x.sum;
		node t1=node(0,pl,lm)+node(0,x.pl,sum+x.lm);
		node t2=node(x.pr,0,x.rm)+node(pr,0,rm+x.sum);
		node t3=node(ml,mr,mm)+node(x.ml,x.mr,x.mm)+node(pr,x.pl,rm+x.lm);
		res.lm=t1.sum,res.pl=t1.r;
		res.rm=t2.sum,res.pr=t2.l;
		res.mm=t3.sum,res.ml=t3.l,res.mr=t3.r;
		return res;
	}
};
pair<seg,seg> tr[maxn<<2];
il void pushup(int id){
	tr[id].fir=tr[lid].fir+tr[rid].fir;
	tr[id].sec=tr[lid].sec+tr[rid].sec;
}
il void pushtag(int id){
	tag[id]^=1;
	swap(tr[id].fir,tr[id].sec);
}
il void pushdown(int id){
	if(tag[id]){
		pushtag(lid),pushtag(rid);
		tag[id]=0;
	}
}
il void build(int id,int l,int r){
	if(l==r){
		tr[id].fir=seg(b[l],b[l],b[l],b[l],l,l,l,l);
		tr[id].sec=seg(-b[l],-b[l],-b[l],-b[l],l,l,l,l);
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pushup(id);
}
il void upd(int id,int L,int R,int l,int r){
	if(L>=l&&R<=r){
		pushtag(id);
		return ;
	}
	pushdown(id);
	int mid=(L+R)>>1;
	if(l<=mid){
		upd(lid,L,mid,l,r);
	}
	if(r>mid){
		upd(rid,mid+1,R,l,r);
	}
	pushup(id);
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	int cnt=0;
	for(int i=1;i<=n;i++){
		cin>>a[i]>>b[i];
		if(a[i]){
			cnt+=b[i],b[i]=-b[i];
		}
	}
	build(1,1,n);
	if(tr[1].fir.lm>0){
		cnt+=tr[1].fir.lm;
		upd(1,1,n,1,tr[1].fir.pl);
	}
	for(int i=1;i<=n;i++){
		ans[i]=ans[i-1]+tr[1].fir.mm;
		upd(1,1,n,tr[1].fir.ml,tr[1].fir.mr);
	}
	for(int i=1;i<=n;i++){
		ans[i]=max(ans[i-1],ans[i]);
	}
	for(int i=0;i<=n;i++){
		cout<<cnt+ans[i]<<"\n";
	}
	return 0;
}
}
signed main(){return asbt::main();}

2025CSP-S模拟赛31

A B C D Sum Rank
50 100 30 49 229 5/18

A. 花海

一个暴力的做法是枚举 \(x_1\)\(x_2\)\(y_1\)\(y_2\),此时的权值即为 \(sum_{x_2,y_2}-sum_{x_1-1,y_2}-sum_{x_2,y_1-1}+sum_{x_1-1,y_1-1}-sum_{x_2-1,y_2-1}+sum_{x_1,y_2-1}+sum_{x_2-1,y_1}-sum_{x_1,y_1}\)。考虑令 \(n\le m\),于是 \(n\le\sqrt{2\times10^5}\)。枚举 \(x_1\)\(x_2\)\(y_1\),动态维护 \(y_2\) 的最大贡献。具体地,倒过来枚举 \(y_1\),用当前的 \(y_1\) 更新答案后再更新 \(sum_{x_2,y_2}-sum_{x_1-1,y_2}-sum_{x_2-1,y_2-1}+sum_{x_1,y_2-1}\) 的最大值。时间复杂度 \(O(n^2m)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=455,inf=1e9;
int n,m;
vector<int> a[maxn];
int main(){
	freopen("flower.in","r",stdin);
	freopen("flower.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	if(n<m){
		for(int i=0;i<=n;i++){
			a[i].resize(m+5);
		}
//		for(int i=0;i<=n;i++){
//			for(int j=0;j<=m;j++){
//				cout<<a[i][j]<<" ";
//			}
//			cout<<"\n";
//		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				cin>>a[i][j];
			}
		}
	}
	else{
		swap(n,m);
		for(int i=0;i<=n;i++){
			a[i].resize(m+5);
		}
		for(int j=1;j<=m;j++){
			for(int i=1;i<=n;i++){
				cin>>a[i][j];
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
//			cout<<a[i][j]<<" ";
			a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
		}
//		cout<<"\n";
	}
	int ans=-inf;
	for(int x1=1;x1<=n;x1++){
		for(int x2=x1+1;x2<=n;x2++){
			int tmp=-inf;
			for(int y1=m;y1;y1--){
				ans=max(ans,tmp-a[x2][y1-1]+a[x1-1][y1-1]+a[x2-1][y1]-a[x1][y1]);
				tmp=max(tmp,a[x2][y1]-a[x1-1][y1]-a[x2-1][y1-1]+a[x1][y1-1]);
			}
		}
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

B. 划分

考虑 DP,设 \(dp_i\) 表示考虑了前 \(i\) 个位置的最小规模和,于是有转移:

\[dp_i=\min_{\operatorname{sum}[j+1,i]\le m}\{dp_j+\max[j+1,i]\} \]

满足条件的 \(j\) 显然是一段连续区间,可以二分出来。用单调栈维护区间 \(\max\) 的变化,线段树优化即可。时间复杂度线性对数。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define lwrb lower_bound
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=1e5+5,inf=1e18;
int n,m,a[maxn],sa[maxn],stk[maxn],top;
int mn[maxn<<2],tag[maxn<<2],dp[maxn];
il void pushup(int id){
	mn[id]=min(mn[lid],mn[rid]);
}
il void pushtag(int id,int v){
	tag[id]+=v,mn[id]+=v;
}
il void pushdown(int id){
	if(tag[id]){
		pushtag(lid,tag[id]);
		pushtag(rid,tag[id]);
		tag[id]=0;
	}
}
il void add(int id,int L,int R,int l,int r,int v){
	if(l>r){
		return ;
	}
	if(L>=l&&R<=r){
		pushtag(id,v);
		return ;
	}
	pushdown(id);
	int mid=(L+R)>>1;
	if(l<=mid){
		add(lid,L,mid,l,r,v);
	}
	if(r>mid){
		add(rid,mid+1,R,l,r,v);
	}
	pushup(id);
}
il int query(int id,int L,int R,int l,int r){
	if(L>=l&&R<=r){
		return mn[id];
	}
	pushdown(id);
	int mid=(L+R)>>1,res=inf;
	if(l<=mid){
		res=min(res,query(lid,L,mid,l,r));
	}
	if(r>mid){
		res=min(res,query(rid,mid+1,R,l,r));
	}
	return res;
}
struct{
	int st[maxn][18],Log[maxn];
	il void build(){
		for(int i=2;i<=n;i++){
			Log[i]=Log[i>>1]+1;
		}
		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]=max(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 max(st[l][p],st[r-(1<<p)+1][p]);
	}
}ST;
int main(){
	freopen("split.in","r",stdin);
	freopen("split.out","w",stdout);
//	freopen("split3.in","r",stdin);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sa[i]=sa[i-1]+a[i];
	}
	if(n<=1e3){
		ST.build();
		memset(dp,0x3f,sizeof(dp));
		dp[0]=0;
		for(int i=1;i<=n;i++){
			for(int j=i-1;~j;j--){
				if(sa[i]-sa[j]>m){
					break;
				}
				dp[i]=min(dp[i],dp[j]+ST.query(j+1,i));
			}
		}
	}
	else{
		stk[++top]=0;
		for(int i=1;i<=n;i++){
			add(1,0,n,i-1,i-1,dp[i-1]+a[i]);
			while(top&&a[stk[top]]<a[i]){
				add(1,0,n,stk[top-1],stk[top]-1,a[i]-a[stk[top]]);
				top--;
			}
			stk[++top]=i;
			dp[i]=query(1,0,n,lwrb(sa,sa+i,sa[i]-m)-sa,i-1);
		}
	}
	cout<<dp[n];
	return 0;
}
}
signed main(){return asbt::main();}

C. 落子无悔

对于两个序列 \(a\)\(b\),我们设 \(s0\)\(s1\) 分别为 \(0\)\(1\) 的数量,由邻项交换可知 \(a\) 排在 \(b\) 前面更优等价于 \(\frac{s0_a}{s1_a}\ge\frac{s0_b}{s1_b}\)。于是我们的做法就比较显然了:每次选出 \(\frac{s0}{s1}\) 最大的点,将其与父亲节点合并,用 set 维护即可。注意把根特判掉。时间复杂度线性对数。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e5+5;
int n,fa[maxn],p[maxn],a[maxn];
int _0[maxn],_1[maxn],nxt[maxn],lst[maxn];
il int find(int x){
	return x!=p[x]?p[x]=find(p[x]):x;
}
struct node{
	int u,_0,_1;
	node(int u=0,int _0=0,int _1=0):u(u),_0(_0),_1(_1){}
	il bool operator<(const node &x)const{
		if(_0*1ll*x._1==x._0*1ll*_1){
			return u<x.u;
		}
		return _0*1ll*x._1>x._0*1ll*_1;
	}
};
set<node> S;
int main(){
	freopen("board.in","r",stdin);
	freopen("board.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=2;i<=n;i++){
		cin>>fa[i];
	}
	for(int i=1;i<=n;i++){
		cin>>a[i];
		_0[i]=a[i]^1,_1[i]=a[i];
		p[i]=i,nxt[i]=lst[i]=i;
	}
	for(int i=2;i<=n;i++){
		S.insert(node(i,_0[i],_1[i]));
	}
//	for(node x:S){
//		cout<<x.u<<" "<<x._0<<" "<<x._1<<"\n";
//	}
	for(int i=1;i<n;i++){
//		cout<<i<<"\n";
		int u=S.begin()->u,v=find(fa[u]);
		S.erase(node(u,_0[u],_1[u]));
		if(v!=1){
			S.erase(node(v,_0[v],_1[v]));
		}
		_0[v]+=_0[u],_1[v]+=_1[u];
		nxt[lst[v]]=u,lst[v]=lst[u];
		p[u]=v;
		if(v!=1){
			S.insert(node(v,_0[v],_1[v]));
		}
	}
	ll cnt=0,ans=0;
	for(int i=1;;i=nxt[i]){
//		cout<<i<<"\n";
		if(!a[i]){
			ans+=cnt;
		}
		else{
			cnt++;
		}
		if(i==nxt[i]){
			break;
		}
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

D. 体测

2025CSP-S模拟赛33

A B C D Sum Rank
100 100 30 - 230 3/20

A. 机器人

dfs,暴力枚举每一步有没有遇到障碍物,用 set 维护答案即可。

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
using namespace std;
namespace asbt{
int n;
bool vis[50][50],ban[50][50];
string s;
set<pii> S;
il void dfs(int x,int y,int d){
	if(d>n){
		S.insert(mp(x,y));
		return ;
	}
	int nx,ny;
	switch(s[d]){
		case 'L':{
			nx=x-1,ny=y;
			break;
		}
		case 'R':{
			nx=x+1,ny=y;
			break;
		}
		case 'D':{
			nx=x,ny=y-1;
			break;
		}
		default:{
			nx=x,ny=y+1;
			break;
		}
	}
	if(!vis[nx+20][ny+20]){
		vis[nx+20][ny+20]=1;
		dfs(nx,ny,d+1);
		ban[nx+20][ny+20]=1;
		dfs(x,y,d+1);
		ban[nx+20][ny+20]=0;
		vis[nx+20][ny+20]=0;
	}
	else if(ban[nx+20][ny+20]){
		dfs(x,y,d+1);
	}
	else{
		dfs(nx,ny,d+1);
	}
}
int main(){
//	system("fc robot3.ans my.out");
//	freopen("robot3.in","r",stdin);
//	freopen("my.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>s;
	s=" "+s;
	vis[20][20]=1;
	dfs(0,0,1);
	cout<<S.size()<<"\n";
	for(pii x:S){
		cout<<x.fir<<" "<<x.sec<<"\n";
	}
	return 0;
}
}
int main(){return asbt::main();}

B. 旅行

首先考虑一个暴力的思路:将每个点拆成若干个点,代表可能的颜色,对边设点,将边与端点对应的颜色用并查集合并即可。发现每次合并在一段时间后就会撤销,使用可撤销并查集,线段树分治即可。时间复杂度线性对数方,需要卡常。

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
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
namespace IO{
	const int bufsz=1<<20;
	char ibuf[bufsz],*p1=ibuf,*p2=ibuf;
	#define getchar() (p1==p2&&(p2=(p1=ibuf)+fread(ibuf,1,bufsz,stdin),p1==p2)?EOF:*p1++)
	il int read(){
		char ch=getchar();
		while(ch<'0'||ch>'9'){
			ch=getchar();
		}
		int x=0u;
		while(ch>='0'&&ch<='9'){
			x=(x<<1)+(x<<3)+(ch^48);
			ch=getchar();
		}
		return x;
	}
	#undef getchar
	char obuf[bufsz],*p3=obuf,s[50];
	#define flush() (fwrite(obuf,1,p3-obuf,stdout),p3=obuf)
	#define putchar(ch) (p3==obuf+bufsz&&flush(),*p3++=(ch))
	il void write(int x){
		int top=0u;
		do{
			s[++top]=x%10|48;
			x/=10;
		}while(x);
		while(top){
			putchar(s[top--]);
		}
		putchar('\n');
	}
	#undef putchar
	struct FL{
		~FL(){
			flush();
		}
	}fl;
	#undef flush
}
using IO::read;
using IO::write;
const int maxn=1e5+5;
int T,n,m,cnt,tot,top,col[maxn],pos[maxn],ban[maxn];
int fa[maxn*5],sz[maxn*5],tz[maxn*5],ans[maxn];
pii stk[maxn<<1];
map<pii,int> bid;
map<int,int> did[maxn];
vector<pii> g[maxn<<2];
struct{
	int u,v,w;
}e[maxn];
il int find(int x){
	return x!=fa[x]?find(fa[x]):x;
}
il void merge(int u,int v){
	u=find(u),v=find(v);
	if(u==v){
		return ;
	}
	if(sz[u]>sz[v]){
		swap(u,v);
	}
	stk[++top]=mp(u,v);
	sz[v]+=sz[u],fa[u]=v;
	if(tz[u]&&tz[v]){
		--tot;
	}
	tz[v]+=tz[u];
}
il void che(){
	int u=stk[top].fir,v=stk[top].sec;
	--top,fa[u]=u,sz[v]-=sz[u],tz[v]-=tz[u];
	if(tz[u]&&tz[v]){
		++tot;
	}
}
int l,r;
pii x;
il void add(int id,int L,int R){
	if(L>=l&&R<=r){
		g[id].pb(x);
		return ;
	}
	int mid=(L+R)>>1;
	if(l<=mid){
		add(lid,L,mid);
	}
	if(r>mid){
		add(rid,mid+1,R);
	}
}
il void solve(int id,int l,int r){
	int ttp=top;
	for(pii i:g[id]){
		merge(i.fir,i.sec);
	}
	if(l==r){
		ans[l]=tot;
	}
	else{
		int mid=(l+r)>>1;
		solve(lid,l,mid);
		solve(rid,mid+1,r);
	}
	while(top>ttp){
		che();
	}
	g[id].clear();
}
int main(){
//	system("fc tour3.ans my.out");
//	freopen("tour3.in","r",stdin);
//	freopen("my.out","w",stdout);
	T=read();
	while(T--){
		cnt=n=read(),m=read();
		bid.clear();
		for(int i=1u;i<=n;++i){
			did[i].clear();
			ban[i]=0u;
		}
		for(int i=1u,u,v,w;i<=n;++i){
			u=read(),v=read(),w=read();
//			cout<<u<<" "<<v<<" "<<w<<"\n";
			if(u>v){
				swap(u,v);
			}
			did[u][w]=++cnt;
			did[v][w]=++cnt;
			if(bid.count(mp(u,v))){
				ban[bid[mp(u,v)]]=i;
			}
			else{
				bid[mp(u,v)]=i;
			}
			col[i]=w,pos[i]=0u;
		}
		for(int i=1u,u,v,w;i<=m;++i){
			u=read(),v=read(),w=read();
			if(u>v){
				swap(u,v);
			}
			did[u][w]=++cnt;
			did[v][w]=++cnt;
			e[i]={u,v,w};
		}
		for(int i=1u;i<=m;++i){
			int u=e[i].u,v=e[i].v,w=e[i].w;
			int k=bid[mp(u,v)];
			if(col[k]==w){
				continue;
			}
			l=pos[k],r=i-1;
			x=mp(k,did[u][col[k]]),add(1u,0u,m);
			x=mp(k,did[v][col[k]]),add(1u,0u,m);
			col[k]=w,pos[k]=i;
			if(ban[k]){
				k=ban[k];
				l=pos[k],r=i-1;
				x=mp(k,did[u][col[k]]),add(1u,0u,m);
				x=mp(k,did[v][col[k]]),add(1u,0u,m);
				col[k]=w,pos[k]=i;
			}
		}
		for(pair<pii,int> i:bid){
			int u=i.fir.fir,v=i.fir.sec,k=i.sec;
			l=pos[k],r=m;
			x=mp(k,did[u][col[k]]),add(1u,0u,m);
			x=mp(k,did[v][col[k]]),add(1u,0u,m);
			if(ban[k]){
				k=ban[k];
				l=pos[k],r=m;
				x=mp(k,did[u][col[k]]),add(1u,0u,m);
				x=mp(k,did[v][col[k]]),add(1u,0u,m);
			}
		}
		for(int i=1u;i<=cnt;++i){
			fa[i]=i,sz[i]=1u,tz[i]=i<=n;
		}
		tot=n,solve(1u,0u,m);
//		cerr<<ans[0]<<"\n";
		for(int i=1u;i<=m;++i){
			write(ans[i]);
		}
	}
//	cerr<<mx<<"\n";
	return 0;
}
}
int main(){return asbt::main();}

C. 点餐

考虑第 \(i\) 个客人怎么做,将 \(b\) 升序排序,枚举 \(j\ge i\),强制选择 \(j\),那么 \(b\) 的最大值即为 \(b_j\),再在前 \(j-1\) 个位置中选择前 \(i-1\) 小的 \(a\) 即可,需要主席树。进一步可以发现的是,最优决策点是有单调性的。于是分治即可。时间复杂度线性对数方。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define lwrb lower_bound
using namespace std;
namespace asbt{
const int maxn=2e5+5,maxm=maxn*20,inf=1e18;
int n,tot,lsh[maxn],tong[maxn],rt[maxn],ans[maxn];
int cnt,ls[maxm],rs[maxm],sz[maxm],sum[maxm];
struct node{
	int a,b;
}p[maxn];
il void pushup(int id){
	sz[id]=sz[ls[id]]+sz[rs[id]];
	sum[id]=sum[ls[id]]+sum[rs[id]];
}
il void insert(int &p,int q,int l,int r,int x){
	p=++cnt;
	if(l==r){
		sum[p]=lsh[x],sz[p]=1;
		return ;
	}
	int mid=(l+r)>>1;
	if(x<=mid){
		rs[p]=rs[q];
		insert(ls[p],ls[q],l,mid,x);
	}
	else{
		ls[p]=ls[q];
		insert(rs[p],rs[q],mid+1,r,x);
	}
	pushup(p);
}
il int query(int id,int l,int r,int x){
	if(!id||!x){
		return 0;
	}
	if(sz[id]==x){
		return sum[id];
	}
	int mid=(l+r)>>1;
	if(x<=sz[ls[id]]){
		return query(ls[id],l,mid,x);
	}
	return sum[ls[id]]+query(rs[id],mid+1,r,x-sz[ls[id]]);
}
il int calc(int x,int y){
//	cout<<x<<" "<<y<<" "<<query(rt[y-1],1,n,x-1)<<"\n";
	return query(rt[y-1],1,n,x-1)+lsh[p[y].a]+p[y].b;
}
il void solve(int L,int R,int l,int r){
	if(L>R){
		return ;
	}
	int mid=(L+R)>>1;
	int &res=ans[mid]=inf,p=-1;
	for(int i=max(l,mid);i<=r;i++){
		int t=calc(mid,i);
		if(t<res){
			res=t,p=i;
		}
	}
//	cout<<mid<<" "<<p<<" "<<calc(mid,p)<<"\n";
	solve(L,mid-1,l,p);
	solve(mid+1,R,p,r);
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>p[i].a>>p[i].b;
		lsh[++tot]=p[i].a;
	}
	sort(lsh+1,lsh+tot+1);
	for(int i=1;i<=n;i++){
		int t=lwrb(lsh+1,lsh+tot+1,p[i].a)-lsh;
		p[i].a=t+tong[t],tong[t]++;
	}
	sort(p+1,p+n+1,[](node x,node y){return x.b<y.b||x.b==y.b&&x.a<y.a;});
	for(int i=1;i<=n;i++){
		insert(rt[i],rt[i-1],1,n,p[i].a);
	}
	solve(1,n,1,n);
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<"\n";
	}
	return 0;
}
}
signed main(){return asbt::main();}

D. 无穷括号序列

\(f_{k,i}\) 表示 \(B^k\) 中第 \(i\) 个左括号的下标,于是答案可以通过差分+二分求出,问题变为求 \(f\)

考虑转移,分两种情况:

  • \(f_{k-1,i}\) 下一个位置为右括号,() 会变成 )(,于是 \(f_{k,i}=f_{k-1,i}+1\)

  • \(f_{k+1,i}\) 下一个位置为左括号,(( 会变成 (*,于是 \(f_{k,i}=f_{k-1,i+1}-1\)

于是 \(f_{k,i}=\min(f_{k-1,i}+1,f_{k-1,i+1}-1)\)

我们假设 \(f_{k,i}\) 是从 \(f_{0,j}\) 转移过来的,于是转移过程中 \(\min\) 中的第二项选了 \(j-i\) 次,第一项选了 \(k-j+i\) 次。于是有:

\[f_{k,i}=\min_{j=i}^{i+k}\{f_{0,j}+k-2j+2i\} \]

考虑用 ST 表维护 \(F_j=f_{0,j}-2j\) 的最小值,但是序列太长了。记序列 \(A\) 中有 \(m\) 个左括号,于是 \(f_{0,j+m}=f_{0,j}+n\)。于是:

\[F_j=F_{j\bmod m}+(n-2m)\times\lfloor\frac{j}{m}\rfloor \]

于是只需要预处理出 \(F_0\)\(F_{2m-1}\) 即可。但是如果 \(k>m\),我们依然求不了,考虑 \(n\)\(2m\) 的关系。

  • \(n\ge2m\),则 \(F_{j+m}\ge F_j\),最小值在 \([i,i+m)\) 中。

  • \(n<2m\),类似地,最小值在 \((i+k-m,i+k]\) 中。

这样就又可以求了。轻微卡常,减少取模即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e5+5,inf=1e9;
int T,n,m,q,f[maxn],Log[maxn],st[maxn][19];
string s;
il int query(int l,int r){
	int p=Log[r-l+1];
	return min(st[l][p],st[r-(1<<p)+1][p]);
}
il int F(int k,int i){
	#define qum(x) (x=x>=0?x:x+m)
	if(k<=m){
		int p=i%m;qum(p);
		int t=(i-p)/m;
		return query(p,p+k)+k+2*i+(n-2*m)*t;
	}
	if(n>=m<<1){
		int p=i%m;qum(p);
		int t=(i-p)/m;
		return query(p,p+m-1)+k+2*i+(n-2*m)*t;
	}
	int p=(i+k)%m;qum(p);
	int t=(i+k-m-p)/m;
	return query(p+1,p+m)+k+2*i+(n-2*m)*t;
	#undef qum
}
il int g(int k,int p){
	int l=-inf,r=inf;
	while(l<r){
		int mid=(l+r+1)>>1;
		if(F(k,mid)<=p){
			l=mid;
		}
		else{
			r=mid-1;
		}
	}
	return l;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	for(int i=2;i<=2e5;i++){
		Log[i]=Log[i>>1]+1;
	}
	cin>>T;
	while(T--){
		cin>>s>>q;
		n=s.size(),m=0;
		for(int i=0;i<n;i++){
			if(s[i]=='('){
				f[m++]=i;
			}
		}
		if(!m){
			while(q--){
				int k,l,r;
				cin>>k>>l>>r;
				cout<<0<<'\n';
			}
			continue;
		}
		for(int i=0;i<m;i++){
			f[i+m]=f[i]+n;
		}
		for(int i=0;i<m<<1;i++){
			st[i][0]=f[i]-i*2;
		}
		for(int j=1;j<=17;j++){
			for(int i=0;i+(1<<j)-1<m<<1;i++){
				st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
			}
		}
		while(q--){
			int k,l,r;
			cin>>k>>l>>r;
			cout<<g(k,r)-g(k,l-1)<<'\n';
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

2025CSP-S模拟赛34(牛宏昊)

A B C D Sum Rank
50 60 0 35 145 3/17

A. 酒吧

假设我们最终选的是 \(b_1,b_2,\dots,b_k\),那么答案为 \(\sum_{i=1}^{k-1}(b_{i+1}-b_i)\times(p_{b_i}+p_{b_{i+1}})\)。发现这实际上是梯形面积的两倍。那么我们相当于是要在所有的点 \((i,p_i)\) 中选一些点使线下面积最大,用单调栈维护上凸包即可。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e5+5;
int n,a[maxn],q[maxn],tl; 
int main(){
	freopen("bar.in","r",stdin);
	freopen("bar.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	#define K(i,j) (a[j]-a[i])*1.0l/(j-i)
	for(int i=1;i<=n;i++){
		while(tl>1&&K(q[tl-1],q[tl])<K(q[tl],i)){
			tl--;
		}
		q[++tl]=i;
	}
	#undef K
	int ans=0;
	for(int i=1;i<tl;i++){
		ans+=(q[i+1]-q[i])*(a[q[i]]+a[q[i+1]]);
	}
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}

B. 逆转

考虑二分答案 \(mid\),求出 \(f_i\) 表示从 \(1\)\(i\) 和不超过 \(mid\) 最多能选多少个,\(g_i\) 类似地表示后缀。考虑在 \(1\)\(i\) 中尽可能多选,显然从小到大选最优。于是用大根堆维护,若能加入则直接加,否则若比堆顶小则替换堆顶即可。时间复杂度线性对数方。CF上卡常

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
namespace IO{
	const int bufsz=1<<20;
	char ibuf[bufsz],*p1=ibuf,*p2=ibuf;
	#define getchar() (p1==p2&&(p2=(p1=ibuf)+fread(ibuf,1,bufsz,stdin),p1==p2)?EOF:*p1++)
	il int read(){
		char ch=getchar();
		while(ch<'0'||ch>'9'){
			ch=getchar();
		}
		int x=0;
		while(ch>='0'&&ch<='9'){
			x=(x<<1)+(x<<3)+(ch^48);
			ch=getchar();
		}
		return x;
	}
	#undef getchar
	char obuf[bufsz],*p3=obuf,s[50];
	#define flush() (fwrite(obuf,1,p3-obuf,stdout),p3=obuf)
	#define putchar(ch) (p3==obuf+bufsz&&flush(),*p3++=(ch))
	il void write(ll x){
		int top=0;
		do{
			s[++top]=x%10|48;
			x/=10;
		}while(x);
		while(top){
			putchar(s[top--]);
		}
	}
	struct FL{
		~FL(){
			flush();
		}
	}fl;
	#undef flush
	#undef putchar
}
using IO::read;
using IO::write;
const int maxn=3e5+5;
int T,n,m,a[maxn],f[maxn],g[maxn];
il bool check(ll x){
	ll sum=0;
	priority_queue<int> q;
	for(int i=1;i<=n;i++){
		if(sum+a[i]<=x){
			q.push(a[i]),sum+=a[i];
		}
		else if(q.size()&&a[i]<q.top()){
			sum-=q.top(),q.pop();
			q.push(a[i]),sum+=a[i];
		}
		f[i]=q.size();
	}
	sum=0;
	while(q.size()){
		q.pop();
	}
	for(int i=n;i;i--){
		if(sum+a[i]<=x){
			q.push(a[i]),sum+=a[i];
		}
		else if(q.size()&&a[i]<q.top()){
			sum-=q.top(),q.pop();
			q.push(a[i]),sum+=a[i];
		}
		g[i]=q.size();
	}
	f[0]=g[n+1]=0;
	for(int i=0;i<=n;i++){
		if(f[i]+g[i+1]>=m){
			return 1;
		}
	}
	return 0;
}
int main(){
	freopen("ace.in","r",stdin);
	freopen("ace.out","w",stdout);
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
	}
	ll l=1,r=3e14;
	while(l<r){
		ll mid=(l+r)>>1;
		if(check(mid)){
			r=mid;
		}
		else{
			l=mid+1;
		}
	}
	write(l);
	return 0;
}
}
int main(){return asbt::main();}

C. 世界

\(f_{i,j}\) 表示 \(n-i,k=j\) 的答案,于是有方程:

\[f_{i,j}=\frac{(m-i)(i-j+1)}{(i+1)m}f_{i+1,j}+\frac{(m-i)j}{m(i+1)}f_{i+1,j+1}+\frac{i}{m(i-1)}(\sum_{k=j}^{i-1}f_{k,j}+\sum_{k=1}^{j-1}f_{i-k,j-k}) \]

\(f_{i+1,j+1}\) 移项到一边,发现另一边的第二维都 \(\le j\)。但是此时我们不能递推,因为上面那个式子中 \(j\ne1\)。也就是说,我们需要求出 \(j=2\) 的所有 \(f_{i,j}\)

考虑主元法,将 \(\forall i\in[3,m],f_{i,2}\) 设为主元,做前缀和即可用这 \(m-2\) 个主元表示其它值。注意到我们在表示过程中 \(i\ne m\),而这正好有 \(j\in[2,m-1]\)\(m-2\) 个,于是再用它们的式子高斯消元即可。答案即为 \(f_{n,k}\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=255,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 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;
}
int n,kk,m,inv[maxn];
struct node{
	int a[maxn];
	node(){
		for(int i=1;i<m;i++){
			a[i]=0;
		}
	}
	il int&operator[](int x){
		return a[x];
	}
	il node operator+(node x)const{
		node res;
		for(int i=1;i<m;i++){
			res[i]=pls(a[i],x[i]);
		}
		return res;
	}
	il node operator-(node x)const{
		node res;
		for(int i=1;i<m;i++){
			res[i]=sub(a[i],x[i]);
		}
		return res;
	}
	il node operator*(int x)const{
		node res;
		for(int i=1;i<m;i++){
			res[i]=a[i]*1ll*x%mod;
		}
		return res;
	}
}a[maxn][maxn],f[maxn][maxn],g[maxn][maxn],h[maxn];
il void gsjd(int n){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n+1;j++){
			cout<<h[i][j]<<" ";
		}
		cout<<"\n";
	}
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++){
			if(h[j][i]){
				swap(h[i],h[j]);
				break;
			}
		}
		int t=qpow(h[i][i]);
		for(int j=1;j<=n;j++){
			if(i==j){
				continue;
			}
			for(int k=i+1;k<=n+1;k++){
				h[j][k]=sub(h[j][k],h[j][i]*1ll*t%mod*h[i][k]%mod);
			}
		}
	}
	for(int i=1;i<=n;i++){
		h[i][n+1]=h[i][n+1]*1ll*qpow(h[i][i])%mod;
	}
}
int main(){
	freopen("world.in","r",stdin);
	freopen("world.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>kk>>m;
	if(kk==1||kk==n){
		cout<<n;
		return 0;
	}
	inv[1]=1;
	for(int i=2;i<=m;i++){
		inv[i]=inv[mod%i]*1ll*(mod-mod/i)%mod;
	}
	for(int i=1;i<=m;i++){
		a[i][1][m-1]=a[i][i][m-1]=i;
	}
	for(int i=3;i<=m;i++){
		a[i][2][i-2]=1;
	}
	for(int j=1;j<=m;j++){
		for(int i=j;i<=m;i++){
			f[i][j]=f[i-1][j]+a[i][j];
			g[i][j]=g[i-1][j-1]+a[i][j];
		}
		if(j>1){
			for(int i=j+1;i<m;i++){
				a[i+1][j+1]=a[i][j]*(m*1ll*(i+1)%mod*inv[m-i]%mod*inv[j]%mod)-a[i+1][j]*((i-j+1)*1ll*inv[j]%mod)-(f[i-1][j]+g[i-1][j-1])*(i*1ll*(i+1)%mod*inv[i-1]%mod*inv[m-i]%mod*inv[j]%mod);
			}
		}
	}
	for(int i=2;i<m;i++){
		h[i-1]=(f[m-1][i]+g[m-1][i-1])*inv[m-1]-a[m][i];
		h[i-1][m-1]=(mod-h[i-1][m-1])%mod;
	}
	gsjd(m-2);
	int ans=0;
	for(int i=1;i<=m-2;i++){
//		cout<<a[n][kk][i]<<" ";
		ans=pls(ans,a[n][kk][i]*1ll*h[i][m-1]%mod);
	}
//	cout<<"\n";
	cout<<pls(ans,a[n][kk][m-1]);
	return 0;
}
}
int main(){return asbt::main();}

D. 染色

没动。(一语双关

posted @ 2025-07-30 21:33  zhangxy__hp  阅读(76)  评论(0)    收藏  举报