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)))\),期望得分 \(60\text{ pts}\)。
排序可以在枚举前进行,而且城市之间的连边可以只保留 MST 上的。时间复杂度 \(O(m\log m+(nk)\log(nk)+2^k(nk+\alpha(n)))\)。
进一步考虑,额外改造一个乡村后的 MST,只可能包含原 MST 和新加的 \(n\) 条边。可以通过归并在 \(O((n+k)\alpha(n))\) 内求得。时间复杂度 \(O(2^k(n+k)\alpha(n))\)。
洛谷上的民间数据,后两种做法都能通过。
#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 蛮有启发性的。由于所有串串都由 a 和 b 构成,且 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)\)。
接下来考虑一般情况。
对于模式串对 \(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(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;}
}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;
}