CSP 2025 赛后补题

T1

考虑贪心,首先按照每个人的最优分配社团,如果合法那么直接输出答案否则,将多余的部分调整到贪心地调整到其他社团即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
	char c=getchar();
	int ans=0,f=1;
	while(c<48||c>57) (c==45?f=-1:1),c=getchar();
	while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
	return ans*f;
}
const int N=1e5+10;
struct node{
	int a,b,c;
}a[N];
struct node1{
	int a,b;
}a1[N],a2[N],a3[N];
int b[N];
inline int work(node1 a[],int n){
	int ans=0;
	for (int i=1;i<=n;i++) b[i]=a[i].b-a[i].a,ans+=a[i].a;
	sort(b+1,b+n+1,greater<int>());
	for (int i=1;i<=n/2;i++) ans+=b[i];
	return ans;
}
int cnt[4];
inline void solve(){
	int n=read();
	int sum=0;
	cnt[1]=cnt[2]=cnt[3]=0;
	for (int i=1;i<=n;i++) a[i]={read(),read(),read()},sum+=max(max(a[i].a,a[i].b),a[i].c);
	for (int i=1;i<=n;i++) if (a[i].a==max(max(a[i].a,a[i].b),a[i].c)) cnt[1]++;else if (a[i].b==max(max(a[i].a,a[i].b),a[i].c)) cnt[2]++;else cnt[3]++;
	for (int i=1;i<=n;i++){
		a1[i]={a[i].a,max(a[i].b,a[i].c)};
		a2[i]={a[i].b,max(a[i].a,a[i].c)};
		a3[i]={a[i].c,max(a[i].b,a[i].a)};
	}
	int ans=max(max(work(a1,n),work(a2,n)),work(a3,n));
	if (cnt[1]<=n/2&&cnt[2]<=n/2&&cnt[3]<=n/2) ans=max(ans,sum);
	printf("%lld\n",ans);
}
main(){
	int T=read();
	while(T--) solve();
	return 0;
}

T2

首先我们可以 \(2^k\) 暴力枚举所有的乡镇是否选取,然后代价即为图的最小生成树加上生成的乡镇的代价。

直接做是 \(O(2^k m \log m)\) 的,但是我们可以对一开始的图求最小生成树,在新图中做上面的步骤显然是对的,那么我们将复杂度优化为 \(O(2^k nk \log nk)\),瓶颈在排序,那么我们直接离散化桶排即可。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e4+10,M=2e6+10;
struct node{
	int u,v,w;
	bool operator <(const node &x) const{
		return w<x.w;
	}
}a[M],b[M];
int n,m,k,f[N],w[12][N];
vector<int>e;
inline int get(int x){return lower_bound(e.begin(),e.end(),x)-e.begin()+1;} 
#define pii pair<int,int>
vector<pii >mark[N*15];
inline int read(){
	char c=getchar();
	int ans=0,f=1;
	while(c<48||c>57) (c==45?f=-1:1),c=getchar();
	while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
	return ans*f;
}
int find(int x){
	if (f[x]==x) return x;
	return f[x]=find(f[x]);
}
inline ll get(int n,int m){
	for (int i=1;i<=n;i++) f[i]=i;
	ll ans=0;
	for (int i=1;i<=m;i++){
		int u=a[i].u,v=a[i].v,w=a[i].w;
		if (find(u)^find(v)){
			ans+=w,f[find(u)]=find(v);
		}
	}
	return ans;
}
main(){
	n=read(),m=read(),k=read();
	for (int i=1;i<=m;i++) b[i]={read(),read(),read()};
	for (int i=1;i<=k;i++){
		w[i][0]=read();
		for (int j=1;j<=n;j++) w[i][j]=read(),e.push_back(w[i][j]);
	}
	sort(b+1,b+m+1);
	int mm=0;
	for (int i=1;i<=n;i++) f[i]=i;
	for (int i=1;i<=m;i++){
		int u=b[i].u,v=b[i].v;
		if (find(u)^find(v)){
			f[find(u)]=find(v);
			b[++mm]={u,v,b[i].w};
			e.push_back(b[i].w);
		}
	}
	m=mm;
	sort(e.begin(),e.end()),e.erase(unique(e.begin(),e.end()),e.end());
	int M=e.size();
	for (int i=1;i<=m;i++) b[i].w=get(b[i].w);
	for (int i=1;i<=k;i++) for (int j=1;j<=n;j++) w[i][j]=get(w[i][j]);
	ll ans=1e18;
	for (int i=0;i<(1<<k);i++){
		int np=n,mp=m;
		ll sum=0;
		int cnt=0;
		for (int i=1;i<=M;i++) mark[i].clear();
		for (int i=1;i<=m;i++) mark[b[i].w].push_back({b[i].u,b[i].v});
		for (int j=0;j<k;j++) if ((i>>j)&1){
			np++,sum+=w[j+1][0],mp+=n;
			for (int l=1;l<=n;l++) mark[w[j+1][l]].push_back({np,l});
		}
		for (int i=1;i<=M;i++) for (auto j:mark[i]) a[++cnt]={j.first,j.second,e[i-1]};
		ans=min(ans,sum+get(np,mp));
	}
	cout <<ans;
	return 0;
}

T3

将一个变换 \(S_1 \to S_2\) 表示为 $ABC \to ADC $,其中 \(A,C\) 是最长公共前后缀,那么将其转换为 \(A?BD?C\),其中 \(?\) 为特殊字符。\(T_1 \to T_2\) 同理。直接跑多模匹配即可。用 AC 自动机解决,时间复杂度线性乘字符集大小。

#include<bits/stdc++.h>
using namespace std;
const int N=5e6+10;
int ch[N][28],fail[N],idx,cnt[N],n,q;
#define pii pair<int,int>
vector<pii >xx;
inline int get(char c){
	if (c>='a'&&c<='z') return c-'a';
	if (c=='!') return 26;
	if (c=='#') return 27;
}
inline void insert(string s){
	int n=s.size();s=" "+s;
	int p=0;
	for (int i=1;i<=n;i++){
		int x=get(s[i]);
		if (ch[p][x]) p=ch[p][x];
		else p=ch[p][x]=++idx;
	}
	cnt[p]++; 
}
inline void bfs(){
	queue<int>q;
	for (int i=0;i<28;i++) if (ch[0][i]) q.push(ch[0][i]);
	while(!q.empty()){
		int u=q.front();q.pop();
		for (int i=0;i<28;i++){
			int &v=ch[u][i];
			if (v) fail[v]=ch[fail[u]][i],q.push(v);
			else v=ch[fail[u]][i];
		}
	}
}
inline int ask(string s){
	int n=s.size();s=" "+s;
	int ans=0,p=0;
	for (int i=1;i<=n;i++){
		int x=get(s[i]);
		p=ch[p][x];
		for (int j=p;j&&cnt[j]!=-1;j=fail[j]) ans+=cnt[j],xx.push_back({j,cnt[j]}),cnt[j]=-1;
	}
	return ans;
}
inline string get(string s1,string s2){
	int n=s1.size();s1=" "+s1,s2=" "+s2;
	string s="";
	int x=1;
	while(x<=n&&s1[x]==s2[x]) x++;
	int y=n;
	while(y>0&&s1[y]==s2[y]) y--;
	for (int i=1;i<x;i++) s.push_back(s1[i]);
	s.push_back('!');
	for (int i=x;i<=y;i++) s.push_back(s1[i]);
	for (int i=x;i<=y;i++) s.push_back(s2[i]);
	s.push_back('#');
	for (int i=y+1;i<=n;i++) s.push_back(s2[i]);
	return s;
}
void print(int x){
	if (x>9) print(x/10);
	putchar(x%10^48);
}
inline string read(){
	char c=getchar();
	string s="";
	while(c<'a'||c>'z') c=getchar();
	while(c>='a'&&c<='z') s.push_back(c),c=getchar();
	return s;
} 
main(){
	cin>>n>>q;
	for (int i=1;i<=n;i++){
		string s1=read(),s2=read();
		insert(get(s1,s2));
	}
	bfs();
	while(q--){
		string s1=read(),s2=read();
		xx.clear();
		print(ask(get(s1,s2))),putchar(10);
		for (auto i:xx) cnt[i.first]=i.second;
	}
    return 0;
}

T4

考虑 dp,设 \(f_{i,j,k}\) 表示前 \(i\) 个人,有 \(j\) 个人失败,且有 \(k\) 个人 \(c_i>j\),的方案数。

转移分三种情况讨论:

  1. 这个人来了且面试通过,考虑贡献延后 \(f_{i,j,k} \to f_{i+1,j,k+1}\)

  2. 这个人来了但是面试未通过,那么 \(j\) 将加一,且 \(k\) 将加一,考虑计算所有提前钦定 \(c_x = j+1\) 的贡献,枚举个数 \(l\),设 \(cnt_i\) 表示 \(c_x=i\) 的个数,那么 \(f_{i,j,k}{k+1\choose l} {cnt_{j+1}\choose l} l! \to f_{i+1,j+1,k+1-l}\)

  3. 这个人没来,类似 \(2\) 只是我们需要找到一个人挂掉,设 \(pre\)\(cnt\) 的前缀和,那么 \(f_{i,j,k}(pre_j - (i-k)){k\choose l}{pre_{j+1}\choose l} l! \to f_{i+1,j+1,k-l}\)

最后答案直接枚举有几个人面试失败即可 \(\sum f_{n,j,n-pre_j} (n-pre_j)!\)

#include<bits/stdc++.h>
#define int unsigned int
#define double long double
using namespace std;
const int mod=998244353,N=510;
int n,m,f[N][N][N],cnt[N],pre[N],c[N][N],a[N][N],jc[N];string s;
inline int read(){
	char c=getchar();
	int f=1,ans=0;
	while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
	while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
	return ans*f;
}
main(){
	n=read(),m=read();
	cin>>s;s=" "+s;
	for (int i=1;i<=n;i++) cnt[read()]++;
	pre[0]=cnt[0];
	jc[0]=1;
	for (int i=1;i<=n;i++) pre[i]=pre[i-1]+cnt[i],jc[i]=1ll*jc[i-1]*i%mod;
	c[0][0]=a[0][0]=1;
	for (int i=1;i<=n;i++){
		c[i][0]=a[i][0]=1;
		for (int j=1;j<=i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod,a[i][j]=1ll*c[i][j]*jc[j]%mod; 
	}
	f[0][0][0]=1;
	for (int i=0;i<n;i++)
		for (int j=0;j<=n;j++)
			for (int k=0;k<=n;k++){
				if (s[i+1]==49) f[i+1][j][k+1]=(f[i+1][j][k+1]+f[i][j][k])%mod;
				else{
					for (int l=0;l<=min(k+1,cnt[j+1]);l++)
						f[i+1][j+1][k+1-l]=(f[i+1][j+1][k+1-l]+1ll*f[i][j][k]*c[k+1][l]%mod*a[cnt[j+1]][l]%mod);
				}
				for (int l=0;l<=min(k,cnt[j+1]);l++)
					f[i+1][j+1][k-l]=(f[i+1][j+1][k-l]+1ll*f[i][j][k]*(pre[j]-(i-k))%mod*c[k][l]%mod*a[cnt[j+1]][l]%mod)%mod;
			}
	int ans=0;
	for (int i=0;i<=n;i++) if (n-i>=m) ans=(ans+1ll*f[n][i][n-pre[i]]*jc[n-pre[i]]%mod)%mod;
	cout <<ans;
    return 0;
}
posted @ 2026-01-29 11:51  OTn53_qwq  阅读(2)  评论(0)    收藏  举报