ABC416 解题报告

比赛链接

标 * 的为赛后补题。

A - Vacation Validation

解题思路

签到题,直接检查 \([L,R]\) 中间是否有不是 o 的字符,如果有,输出 No 并退出,否则输出 Yes

参考代码

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,l,r;
string s;
int main()
{
	cin>>n>>l>>r>>s;
	s=' '+s;
	for(int i=l;i<=r;i++)
	{
		if(s[i]!='o')
		{
			cout<<"No\n";
			return 0;
		}
	}
	cout<<"Yes\n";
	return 0;
}

B - 1D Akari

题目大意

给定由 .# 构成的字符串,将尽量多的 . 改为 o 使得任意两个 o 之间至少有一个 #

解题思路

ABC 最难的 B 题。 根据题目条件,只需要让每个 o 和下一个 o 之间至少有一个 # 即可。构造每一个 . 连续段都选一个 . 改为 o 即可获得答案。

参考代码

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 105
using namespace std;
int n;
char c[N],s[N];
int main()
{
	scanf("%s",c+1);
	s[0]='#';
	n=strlen(c+1);
	for(int i=1;i<=n;i++)
	{
		if(c[i]=='#') s[i]='#';
		else if(s[i-1]=='#') s[i]='o';
		else s[i]='.';
	}
	printf("%s\n",s+1);
	return 0;
}

C - Concat (X-th)

题目大意

给定 \(N\) 个字符串 \(S_1 \sim S_n\),求所有的 \(S_{t_1}+S_{t_2}+\cdots+S_{t_K}\) 中字典序第 \(X\) 小的字符串。\(t_i\)\([1,N]\) 中的任意值。

解题思路

考虑到总共有 \(N^K\) 种组合,而 \(N^K\leq 10^5\) ,直接暴力枚举然后排序即可。

参考代码

点击查看代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#define N 15
using namespace std;
int n,k,x;
string s[N];
vector<string> v;
void dfs(int dep,string c)
{
	if(dep==k+1)
	{
		v.push_back(c);
		return;
	}
	for(int i=1;i<=n;i++)
	{
		dfs(dep+1,c+s[i]);
	}
	return;
}
int main()
{
	cin>>n>>k>>x;
	for(int i=1;i<=n;i++) cin>>s[i];
	dfs(1,"");
	sort(v.begin(),v.end());
	cout<<v[x-1]<<'\n';
	return 0;
}

D - Match, Mod, Minimize 2

题目大意

给定两个长度为 \(N\) 的数组 \(A,B\),可以将两个数组任意排列,求任意排列后 \(\sum_{i=1}^{N}((A_i+B_i) \mod M)\) 的最小值。

解题思路

放宽条件,考虑把 \(\mod M\) 去掉,那么答案是定值。如果把 \(\mod M\) 加上,那么如果 \(A_i+B_i\ge M\) ,会让这个定值减少 \(M\)。题目转换为最大化 \(A_i+B_i\ge M\) 的对数

转换成这样就好做了。贪心,对 \(A\)\(B\) 进行排序,从大到小枚举每一个 \(B_i\),在 \(A\) 中找最小的 \(A_j\) 使得 \(A_i+B_j\geq M\)\(A_j\) 未被匹配,如果能找到,答案减 \(M\),否则此时的答案就是最小值。\(i,j\) 可以双指针维护。

时间复杂度为 \(O(N \log N)\),瓶颈在于排序。

参考代码

点击查看代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define N 300005
using namespace std;
long long n,m,a[N],b[N],ans;
void solve()
{
	ans=0;
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),ans+=a[i];
	for(int i=1;i<=n;i++) scanf("%lld",&b[i]),ans+=b[i];
	sort(a+1,a+n+1);
	sort(b+1,b+n+1);
	for(int l=1,r=n;r>=1;r--)
	{
		while(l<=n&&a[l]+b[r]<m) l++;
		if(l==n+1) break;
		ans-=m;
		l++;
	}
	printf("%lld\n",ans);
	return;
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		solve();
	}
	return 0;
}
/*
6
1 3 4 
0 1 2
*/

E - Development

题目大意

给定一个 \(N\) 个点 \(M\) 条边的简单无向图,边有边权,经过一条边需耗费对应边权的时间。此外有 \(K\) 个点被改建为机场,可以耗费 \(T\) 的时间从一个机场到任意一个机场。\(Q\) 次操作,每次操作会添加一条边,改建一个机场或询问 \(sum=\sum_{i=1}^N\sum_{j=1}^{N}f_{i,j}\),其中 \(f_{i,j}\) 表示 \(i\)\(j\) 的最短路,不联通记为 \(0\)

解题思路

注意,下文中,对于不联通的 \((i,j)\)\(f_{i,j}\) 记为 \(\inf\)

发现数据范围允许 \(O(N^2Q)\)\(O(N^3)\),这使得本题的可操作空间变大。初始时,如果将机场视作 \(K\) 阶完全图,可以跑 floyd 求出所有点对最短路,得到初始的 \(f\)

对于加边操作,考虑发现边 \((x,y)\)\(f_{i,j}\) 的影响。一共两种情况:经过这条边,或不经过这条边。对应的式子为 \(f_{i,j}=\min(f_{i,j},\min(f_{i,x}+w_{x,y}+f_{y,j},f_{i,y}+w_{y,x}+f_{x,j}))\),对每一对 \((i,j)\) 都更新 \(f_{i,j}\),复杂度为 \(O(N^2)\)

对于加机场操作,考虑机场对 \(f_{i,j}\) 的贡献,记 \(g_i\)\(i\) 与最近的机场的距离,如果路上乘坐飞机,那么一定是在与 \(i\) 最近的机场上飞机,与 \(j\) 最近的机场下飞机,那么加机场后,先 \(O(NK)\) 地更新 \(g\),再更新 \(f_{i,j}=\min(f_{i,j},g_i+T+g_j)\)

对于询问操作,可以 \(O(N^2)\) 暴力枚举,也可以在更新 \(f\) 时动态维护,参考代码使用后者。

总时间复杂度为 \(O(N^2Q)\)

参考代码

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define inf 0x3f3f3f3f3f3f3f3f
#define N 505
using namespace std;
long long n,m,k,t,ans,q;
long long f[N][N],d[N],minn[N];
bool vis[N];
void change(int x,int y,long long dis)
{
	if(f[x][y]<=dis) return;
	if(f[x][y]>=inf) ans+=dis;
	else ans-=(f[x][y]-dis);
	f[x][y]=dis;
	return;
}
void add_edge(int x,int y,int c)
{
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) if(i!=j)
		{
			change(i,j,min(f[i][x]+f[y][j]+c,f[i][y]+f[x][j]+c));
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=k;j++)
		{
			minn[i]=min(minn[i],f[i][d[j]]);
		}
	}
	return;
}
void add_airport(int x)
{
	if(vis[x]) return;
	d[++k]=x;
	vis[x]=true;
	for(int i=1;i<=n;i++) minn[i]=min(minn[i],f[i][x]);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) if(i!=j)
		{
			change(i,j,minn[i]+minn[j]+t);
		}
	}
	return;
}
int main()
{
	memset(f,0x3f,sizeof(f));
	memset(minn,0x3f,sizeof(minn));
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++) f[i][i]=0; 
	for(int i=1;i<=m;i++)
	{
		long long x,y,c;
		scanf("%lld%lld%lld",&x,&y,&c);
		f[x][y]=min(f[x][y],c);
		f[y][x]=f[x][y];
	}
	scanf("%lld%lld",&k,&t);
	for(int i=1;i<=k;i++) scanf("%lld",&d[i]),vis[d[i]]=true;
	for(int i=1;i<=k;i++)
	{
		for(int j=1;j<=k;j++) if(i!=j)
		{
			f[d[i]][d[j]]=min(f[d[i]][d[j]],t);
		}
	}
	for(int k=1;k<=n;k++)
	{
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)
			{
				f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			ans+=(f[i][j]<inf?f[i][j]:0);
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=k;j++)
		{
			minn[i]=min(minn[i],f[i][d[j]]);
		}
	}
	scanf("%lld",&q);
	while(q--)
	{
		int op;
		scanf("%d",&op);
		if(op==1)
		{
			int x,y,t;
			scanf("%d%d%d",&x,&y,&t);
			add_edge(x,y,t);
		}
		else if(op==2)
		{
			int x;
			scanf("%d",&x);
			add_airport(x);
		}
		else
		{
			printf("%lld\n",ans);
		}
	}
	return 0;
}

*F - Paint Tree 2

题目大意

求大小为 \(N\) 的树上,\(K\) 条不相交的链所覆盖的点权之和最大值。

解题思路

参考 luogu id:Moya_Rao 大佬的题解

考虑树形 DP。设计状态 \(f_{i,j,k}\) 表示子树 \(i\) 内有 \(j\) 条链,点 \(i\) 的状态为 \(k\) 的点权之和的最大值。\(k\) 的定义如下:

  • \(k=0\):子树 \(i\) 中没有一条链经过 \(i\)
  • \(k=1\):子树 \(i\) 中有一条链以 \(i\) 为端点。
  • \(k=2\):子树 \(i\) 中有一条链经过 \(i\) 但不以 \(i\) 为端点。

考虑转移。将子树 \(v\) 转移给子树 \(u\) 的情况有三种:

  1. 毫不相干:没有一条链经过 \((u,v)\),此时 \(f_{u,i+j,p} \leftarrow f_{u,i,p}+f_{u,j,q}\ (i+j\leq K,0\leq p,q <3)\)

  2. \(u\) 为端点:在 \(v\) 中的一条以 \(v\) 为端点的链延伸到 \(u\),此时 \(f_{u,i+j,1}\leftarrow f_{u,i,0}+f_{v,j,1}+w_u\ (i+j\leq K)\)

  3. 链经过 \(u\):两条分别以 \(u\) 为端点和以 \(v\) 为端点的点连接起来,组成新的链,此时 \(f_{u,i+j-1,2} \leftarrow f_{u,i,1}+f_{v,j,1}\ (i+j-1\leq K,i+j\geq 1)\)

初始值为 \(f_{u,0,0}=0,f_{u,1,1}=w_u,f_{u,1,2}=w_u\),注意转移时用临时数组存储一下(树形 DP 最好都这样),复杂度 \(O(9NK)\)

参考代码

点击查看代码
#include<iostream>
#include<cstring>
#include<cstdio>
#define N 200005
using namespace std;
long long n,K,head[N],to[N*2],nxt[N*2],w[N];
void add(int x,int y)
{
	static int now=0;
	now++;
	to[now]=y;
	nxt[now]=head[x];
	head[x]=now;
	return;
}
long long f[N][10][3],ff[10][3];
void dfs(int x,int fa)
{
	f[x][0][0]=0;
	f[x][1][1]=w[x];
	f[x][1][2]=w[x];
	for(int i=head[x];i;i=nxt[i])
	{
		int v=to[i];
		if(v==fa) continue;
		dfs(v,x);
		memset(ff,-0x3f,sizeof(ff));
		for(int j=0;j<=K;j++) for(int k=0;k<=K;k++)
		{
			if(k+j<=K) for(int p=0;p<3;p++) for(int q=0;q<3;q++)
			{
				ff[j+k][p]=max(ff[j+k][p],f[x][j][p]+f[v][k][q]);
			}
			if(k+j<=K) ff[j+k][1]=max(ff[j+k][1],f[x][j][0]+f[v][k][1]+w[x]);
			if(k+j-1<=K&&k+j>0) ff[j+k-1][2]=max(ff[j+k-1][2],f[x][j][1]+f[v][k][1]);
		}
		for(int j=0;j<=K;j++)
		{
			for(int p=0;p<3;p++)
			{
				f[x][j][p]=max(f[x][j][p],ff[j][p]);
				if(x==11&&j==2&&p==0) cerr<<'*';
			}
		}
	}
	return;
}
int main()
{
	memset(f,-0x3f,sizeof(f));
	scanf("%lld%lld",&n,&K);
	for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
	dfs(1,0);
	long long ans=-0x3f3f3f3f3f3f3f3f;
	for(int i=0;i<=K;i++)
	{
		for(int j=0;j<3;j++)
		{
			ans=max(ans,f[1][i][j]);
		}
	}
	printf("%lld\n",ans);
	return 0;
}

*G - Concat (1st)

题目大意

同 C 题,但 \(N,K\) 的数据范围扩大,\(X\) 恒为 \(1\)

解题思路

参考 luogu id:Moya_Rao 大佬的题解(没错,又是这位巨佬)和官方题解

考虑看到该题的第一反应是什么,大部分人会想到对 \(S\) 排序完直接硬钢,但这样连样例三都过不了。观察样例三,发现之所以排序不是最优的,是因为 \(\texttt{c+c}\) 并不比 \(\texttt{cba+c}\) 来得优秀,\(\texttt{c}\) 不应排在 \(\texttt{cba}\) 前面。

考虑换一种比较方式 \(\prec\)\(a\prec b\) 当且仅当 \(a+b<b+a\)\(a+b=b+a \operatorname{and} a<b\),根据官方题解,这样的偏序关系满足传递性,因此,我们就可以依此进行排序。

现在计算答案。贪心地想,我们希望最小的字符串 \(S_{min}\) 重复多次,事实上,\(K=\inf\) 时答案就是 \(S_{min}\) 的循环。然而 \(K\neq\inf\)。因此,我们假设当前的最优解为 \(S_{min}\) 的循环,维护最小的后缀 \(ans\),初始为空串,每次令 \(ans=S_{min}+ans\),并不断用其余的 \(S\) 替代 \(S_{min}\) 看看能不能得到更短的后缀更新 \(ans\)\(|ans|\ge|S_{min}|\) 时停止,此时 \(S_{min}\) 的循环加上 \(ans\) 即为答案。

参考代码

点击查看代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define N 100005
using namespace std;
int n,k;
string s[N];
int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>s[i];
	sort(s+1,s+n+1,[](string a,string b){
		return (a+b)!=(b+a)?(a+b)<(b+a):a<b;
	});
	string ans="";
	while(k&&ans.size()<s[1].size())
	{
		k--;
		string tmp=s[1]+ans;
		for(int i=2;i<=n;i++) tmp=min(tmp,s[i]+ans);
		ans=tmp;
	}
	while(k--) cout<<s[1];
	cout<<ans;
	return 0;
}
posted @ 2025-08-20 19:07  Lijiangjun4  阅读(13)  评论(0)    收藏  举报