[题解]CSP-S 2025 T1~T3 题解

T1. P14361 [CSP-S 2025] 社团招新 / club

Tag:贪心、排序。

因为要求每个社团不超过 \(\dfrac{n}{2}\) 个人,所以无论怎么分配,最多只会有一个社团超出限制。

因此,我们先让每个人选最满意的社团。若存在超出限制的社团,则从中调出一些人,使其恰好剩余 \(\dfrac{n}{2}\) 个人。此时一定没有社团超出限制。

为了让答案尽可能优,调出的人的最大满意度 \(-\) 次大满意度应尽可能小。

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

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
using namespace std;
const int N=1e5+5,inf=1e15;
int t,n,a[5],c[5];
vector<int> v[5];
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>t;
	c[0]=-inf;
	while(t--){
		v[1].clear(),v[2].clear(),v[3].clear();
		cin>>n;
		int ans=0;
		for(int i=1;i<=n;i++){
			int mxp=0,_mxp=0;
			for(int j=1;j<=3;j++){
				cin>>a[j];
				if(a[j]>=a[mxp]) _mxp=mxp,mxp=j;
				else if(a[j]>a[_mxp]) _mxp=j;
			}
			ans+=a[mxp];
			v[mxp].eb(a[mxp]-a[_mxp]);
		}
		for(int i=1;i<=3;i++){
			if((int)v[i].size()>n/2){
				sort(v[i].begin(),v[i].end());
				for(int j=0;j<(int)v[i].size()-n/2;j++) ans-=v[i][j];
			}
		}
		cout<<ans<<"\n";
	}
	return 0;
}

T2. P14362 [CSP-S 2025] 道路修复 / road

Tag:图论、生成树。

我们可以直接 \(O(2^k)\) 地枚举对哪些乡村进行改造,然后将它们与城市的边连上,跑 MST。时间复杂度 \(O(2^k m(\log m+\alpha(n+k)))\),期望得分 \(60\text{ pts}\)

排序可以在枚举前进行,而且城市之间的连边可以只保留 MST 上的。时间复杂度 \(O(m\log m+(nk)\log(nk)+2^k(nk+\alpha(n+k)))\)

进一步考虑,额外改造一个乡村后的 MST,只可能包含原 MST 和新加的 \(n\) 条边。可以通过归并在 \(O((n+k)\alpha(n+k))\) 内求得。时间复杂度 \(O(m\log m+(nk)\log(nk)+2^k(n+k)\alpha(n+k))\)

洛谷上的民间数据,后两种做法都能通过。

#1 - 8.27s
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M=1e6+2,N=1e4+2,K=12,inf=1e18;
int n,m,k,fa[N+K],idx,c[K],ans=inf;
inline int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
struct Ed{int u,v,w;}_e[M],e[N+N*K];
inline bool cmp(Ed a,Ed b){return a.w<b.w;}
bitset<K> vis;
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m>>k;
	for(int i=1,u,v,w;i<=m;i++){
		cin>>u>>v>>w;
		_e[i]={u,v,w};
	}
	sort(_e+1,_e+1+m,cmp);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++){
		int u=find(_e[i].u),v=find(_e[i].v);
		if(u^v) fa[u]=v,e[++idx]=_e[i];
	}
	for(int i=1,x;i<=k;i++){
		cin>>c[i];
		for(int j=1;j<=n;j++){
			cin>>x;
			e[++idx]={n+i,j,x};
		}
	}
	sort(e+1,e+1+idx,cmp);
	for(int sta=(1<<k)-1;~sta;sta--){
		int s=0;
		for(int i=1;i<=k;i++){
			if((sta>>(i-1))&1) vis[i]=1,s+=c[i];
			else vis[i]=0;
		}
		for(int i=1;i<=n+k;i++) fa[i]=i;
		for(int i=1;i<=idx;i++){
			int u=e[i].u,v=e[i].v,w=e[i].w;
			if(u>n&&!vis[u-n]) continue;
			u=find(u),v=find(v);
			if(u^v) fa[u]=v,s+=w;
		}
		ans=min(ans,s);
	}
	cout<<ans<<"\n";
	return 0;
}
#2 - 5.96s
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
using namespace std;
const int M=1e6+2,N=1e4+2,K=11,inf=1e18;
int n,m,k,fa[N+K],c[K],ans=inf;
inline int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
struct Ed{int u,v,w;};
vector<Ed> _e,a[K],e[K];
inline bool cmp(Ed a,Ed b){return a.w<b.w;}
inline int merge(vector<Ed>& a,vector<Ed>& b,vector<Ed>& res){
	auto it=b.begin();
	_e.clear(),res.clear();
	int s=0;
	for(int i=1;i<=n+k;i++) fa[i]=i;
	for(Ed i:a){
		for(;it!=b.end()&&it->w<=i.w;it++) _e.eb(*it);
		_e.eb(i);
	}
	for(;it!=b.end();it++) _e.eb(*it);
	for(Ed i:_e){
		int u=find(i.u),v=find(i.v);
		if(u^v) fa[u]=v,res.eb(i),s+=i.w;
	}
	return s;
}
inline void dfs(int p,int s,int sc){//s为修复边的花费,sc为改造的花费
	if(p>k){
		return ans=min(ans,s+sc),void();
	}
	e[p]=e[p-1],dfs(p+1,s,sc);//不改造
	dfs(p+1,merge(e[p-1],a[p],e[p]),sc+c[p]);//改造
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m>>k;
	for(int i=1,u,v,w;i<=m;i++){
		cin>>u>>v>>w;
		_e.eb(Ed{u,v,w});
	}
	sort(_e.begin(),_e.end(),cmp);
	for(int i=1;i<=n;i++) fa[i]=i;
	int s=0;
	for(Ed i:_e){
		int u=find(i.u),v=find(i.v);
		if(u^v) fa[u]=v,e[0].eb(i),s+=i.w;
	}
	for(int i=1,x;i<=k;i++){
		cin>>c[i];
		for(int j=1;j<=n;j++){
			cin>>x;
			a[i].eb(Ed{n+i,j,x});
		}
		sort(a[i].begin(),a[i].end(),cmp);
	}
	dfs(1,s,0);
	cout<<ans<<"\n";
	return 0;
}

T3. P14363 [CSP-S 2025] 谐音替换 / replace

Tag:字符串、Trie 树、数据结构。

我们可以直接对询问串进行字符串哈希,然后逐位枚举。时间是 \(O(L_1+nL_2)\),期望得分 \(25\text{ pts}\)

特殊性质 B 蛮有启发性的。由于所有串串都由 ab 构成,且 b 恰出现一次,所以所有模式串对 / 询问串对都可以用一个点对 \((x,y,p)\) 来描述。

其中,\(x,y\) 分别表示前串中 b 从左往右数、从右往左数的位置;\(p\) 表示前串到后串 b 位置的变化量。

那么模式串 \((x',y',p')\) 能对询问 \((x,y,p)\) 产生贡献,当且仅当:

  • \(x'\le x\)
  • \(y'\le y\)
  • \(p'=p\)

我们按 \(p\) 分组,每一组内做二维数点即可。时间复杂度 \(O(L_1+L_2+(n+q)(\log L_1+\log (n+q)))\)


接下来考虑一般情况。

对于模式串对 \(s=(s_1,s_2)\),我们可以找到它们第一个和最后一个不同的位置,构成一个极小替换区间 \([l,r]\),记区间内的内容为 \(s_1',s_2'\)

那么 \(s\) 可以表示为 \((l+s_1'+r,l+s_2'+r)\)

同理 \(t\) 可以表示为 \((L+t_1'+R,L+t_2'+R)\)

则模式串对 \((s_1,s_2)\) 能对询问串对 \((t_1,t_2)\) 产生贡献,当且仅当:

  • \(l\)\(L\) 的后缀。
  • \(r\)\(R\) 的前缀。
  • \((s_1',s_2')=(t_1',t_2')\)

对于第三条,我们按哈希值(或用 Trie 树)分组,每个组独立处理即可。

第一、二条描述的前后缀关系,放在 Trie 树上就是祖先和后代的关系。按 DFS 序编号后,同样可以转化为二维数点求解。时间复杂度 \(O(L_1|\Sigma|+L_2+(n+q)(\log L_1+\log (n+q)))\)

这里也可以用 AC 自动机来做:对所有 \(\overline{l\texttt{\#}s_1's_2'\texttt{\#}r}\) 建立 AC 自动机,询问串同样写成 \(\overline{l\texttt{\#}t_1't_2'\texttt{\#}r}\),在上面跑多模式匹配即可。时间复杂度应该是 \(O(L_1|\Sigma|+L_2)\)。但是我不会写(逃

注意先把 \(|t_1|\ne|t_2|\) 判掉。

Ref:代码参照 by 20_200

点击查看代码
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define fi first
#define se second
using namespace std;
using namespace __gnu_pbds;
typedef unsigned long long ull;
const int N=2e5+5,Q=2e5+5,L1=5e6+5,C=26,B=233;
gp_hash_table<ull,int> to;
int n,q,tn,rt[N<<1],idx,ans[Q];
pair<int,int> tmp[N];
inline ull hs(string &s){
	ull a=0;
	for(char i:s) a=a*B+i;
	return a;
}
struct TRIE{
	int f[L1][C],dfn[L1],dfm[L1],idx,tim;
	inline int ins(int x,string &s){
		for(char i:s){
			if(!f[x][i-'a']) f[x][i-'a']=++idx;
			x=f[x][i-'a'];
		}
		return x;
	}
	inline void dfs(int x){
		dfn[x]=++tim;
		for(int i=0;i<C;i++) if(f[x][i]) dfs(f[x][i]);
		dfm[x]=tim;
	}
	inline int qry(int x,string &s){
		for(char i:s){
			if(f[x][i-'a']) x=f[x][i-'a'];
			else return x;//注意找不到应直接 return,否则 x 可能从 0 开始走到错误的节点
		}
		return x;
	}
}tr;
inline int lb(int x){return x&-x;}
struct BIT{
	int s[L1];
	inline void chp(int x,int v){for(;x<=tr.idx;x+=lb(x)) s[x]+=v;}
	inline int qry(int x){int a=0;for(;x;x-=lb(x)) a+=s[x];return a;}
}bit;
struct Point{int x,y,k;bool t;}pt[4*N+Q];
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>q;
	string s1,s2,sl,sr,s;
	for(int i=1;i<=n;i++){
		cin>>s1>>s2,sl=sr=s="";
		int m=s1.size(),l=0,r=m-1;
		while(l<m&&s1[l]==s2[l]) sl+=s1[l++];
		while(r>l&&s1[r]==s2[r]) sr+=s2[r--];
		reverse(sl.begin(),sl.end());
		reverse(sr.begin(),sr.end());
		for(int j=l;j<=r;j++) s+=s1[j],s+=s2[j];
		ull h=hs(s);
		int &x=to[h];
		if(!x) rt[x=++tn]=++tr.idx,rt[++tn]=++tr.idx;
		tmp[i]={tr.ins(rt[x],sl),tr.ins(rt[x+1],sr)};
	}
	for(int i=1;i<=tn;i++) tr.dfs(rt[i]);
	for(int i=1;i<=n;i++){
		pt[++idx]={tr.dfn[tmp[i].fi],tr.dfn[tmp[i].se],1,0};
		pt[++idx]={tr.dfn[tmp[i].fi],tr.dfm[tmp[i].se]+1,-1,0};
		pt[++idx]={tr.dfm[tmp[i].fi]+1,tr.dfn[tmp[i].se],-1,0};
		pt[++idx]={tr.dfm[tmp[i].fi]+1,tr.dfm[tmp[i].se]+1,1,0};
	}
	for(int i=1;i<=q;i++){
		cin>>s1>>s2,sl=sr=s="";
		if(s1.size()!=s2.size()) continue;
		int m=s1.size(),l=0,r=m-1;
		while(l<m&&s1[l]==s2[l]) sl+=s1[l++];
		while(r>l&&s1[r]==s2[r]) sr+=s2[r--];
		reverse(sl.begin(),sl.end());
		reverse(sr.begin(),sr.end());
		for(int j=l;j<=r;j++) s+=s1[j],s+=s2[j];
		ull h=hs(s);
		if(to.find(h)==to.end()) continue;
		int x=to[h];
		pt[++idx]={tr.dfn[tr.qry(rt[x],sl)],tr.dfn[tr.qry(rt[x+1],sr)],i,1};
	}
	sort(pt+1,pt+1+idx,[](Point a,Point b){return a.x==b.x?a.t<b.t:a.x<b.x;});
	for(int i=1;i<=idx;i++){
		if(pt[i].t) ans[pt[i].k]=bit.qry(pt[i].y);
		else bit.chp(pt[i].y,pt[i].k);
	}
	for(int i=1;i<=q;i++) cout<<ans[i]<<"\n";
	return 0;
}

T4. P14364 [CSP-S 2025] 员工招聘 / employ

在补……?

期望得分 \(35+70+70+8=183\)

云斗得分 \(45+72+80+12=209\)(数据太弱了吧)。

实测得分 \(40+72+60+8=180\)


T1 自闭了,除了“还好没有砸太多时间死磕”还能说啥。人除我切,我已经菜如狗了。

本来有个很裸的 \(O(n^3)\) DP 结果临结束二十分钟左右发现大样例有一个数对不上,痛失至少 \(30\text{ pts}\)

T2 写了个 \(60\text{ pts}\) 和特殊性质,大概是 \(70\text{ pts}\)。可惜的是正解(如果 CCF 少爷机发力的话 #1 也算正解罢)并不难想,只需要在开始时将城市之间的非 MST 边剔除掉就写出来了。一方面是主要打暴力去了,一方面是思路本身稍乱吧。学到的:不要假定题目难度顺序,这无异于给自己设限。

T3 暴力打得应该还行(怎么还挂了 \(10\text{ pts}\))。赛时用二维数点打出了特殊性质 B ,其实和正解只差一个 Trie 树(虽然这步转化也比较难想),如果多分配一些时间,也可能会有头绪(这道题第一眼联想到 Trie 树,虽然还没好好读)。

T4。暴力最难打的一次。打了 \(O(n!)\)\(8\text{ pts}\) 后,在特殊性质上徘徊许久。结果愣是没注意到还有 \(m=n\) 这个点。痛失 \(4\text{ pts}\)

一句话总结:想不到正解还挂分就是菜,只打暴力不动脑也是菜,不好好读题不规划时间还是菜。

posted @ 2025-11-02 21:39  Sinktank  阅读(224)  评论(6)    收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2025 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.