CF Round #677 div3 赛后总结

本文详细解析了ACM竞赛中的七道算法题,包括无聊数字的位数求和、书本移动最小次数、优势鱼的选择、构造特定条件的树、轮舞方案计数、矩阵元素选择最大和以及图论中的最短路径优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

运气真好AK了(╯‵□′)╯︵┻━┻

庆祝EEE题没看懂题找规律过了(╯‵□′)╯︵┻━┻

A

题意:
差不多就是说,在1−100001-10000110000范围内,如果一个数字的每一位都是同一个数字,就叫无聊数字,然后会以1,11,111,1111,2,22...1,11,111,1111,2,22...1,11,111,1111,2,22...这样的顺序排列,然后问无聊数字xxx,以及其的排列前面的数字的位数之和是多少,例如:111111,前面有111,然后位数之和为2+1=32+1=32+1=3

暴力模拟啊。

#include<cstdio>
#include<cstring>
using  namespace  std;
inline  int  solve(int  x)
{
	int  type=x%10;
	int  ans=(type-1)*10;
	int  y=0;
	while(x)
	{
		x/=10;
		y++;
		ans+=y;
	}
	printf("%d\n",ans);
}
int  main()
{
	int  T;scanf("%d",&T);
	for(int  i=1;i<=T;i++)
	{
		int  x;scanf("%d",&x);
		solve(x);
	}
	return  0;
}

B

题意:
如果第iii个位置有书为111,没书为000
对于书架上连续的一段书[l,r][l,r][l,r],如果r+1r+1r+1的位置没有位置,则可以把[l,r][l,r][l,r]的书右移到[l+1,r+1][l+1,r+1][l+1,r+1],左移类似。
然后问把所有书变成连续的一段最小需要多少移动次数。

做法:
每次最多消掉一个间隔,不难发现,答案就是相邻每段书之间的间隔和。

#include<cstdio>
#include<cstring>
#define  N  60
using  namespace  std;
int  a[N],n;
void  solve()
{
	scanf("%d",&n);
	for(int  i=1;i<=n;i++)scanf("%d",&a[i]);
	bool  bk=0;int  ans=0,cnt=0;
	for(int  i=1;i<=n;i++)
	{
		if(!a[i])
		{
			if(bk)cnt++;
		}
		else
		{
			bk=1;
			ans+=cnt;
			cnt=0;
		}
	}
	printf("%d\n",ans);
}
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)solve();
	return  0;
}

C

题意:
差不多就是说,一条鱼iii,如果a[i−1]<a[i]a[i-1]<a[i]a[i1]<a[i]或者a[i+1]<a[i]a[i+1]<a[i]a[i+1]<a[i],就可以吃掉i−1i-1i1或者i+1i+1i+1的位置,然后a[i]++a[i]++a[i]++,如果一条鱼,假定全局只有它能吃别的鱼,并且在最后它能吃掉所有的鱼,那么称其为优势鱼,然后问存不存在优势鱼,存在,随便输其中一条优势鱼的编号。

首先考虑权值最大的情况,那如果权值最大的鱼吃掉了一条鱼,那么所有的鱼它随便吃,因此只要存在一只权值最大的鱼且左右两边有一条鱼比它小,它就是优势鱼。

当时这样一定能判定没有优势鱼的情况吗?不难发现,这种做法找到的优势鱼一定是正确的,且如果找不到当且仅当所有的鱼权值相同,此时确实是没有优势鱼的,所以这种做法是正确的。

#include<cstdio>
#include<cstring>
#define  N  310000
using  namespace  std;
int  a[N],n;
inline  int  mymax(int  x,int  y){return  x>y?x:y;}
void  solve()
{
	scanf("%d",&n);
	int  mmax=1;
	for(int  i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		mmax=mymax(a[i],mmax);
	}
	a[0]=a[1];a[n+1]=a[n];
	for(int  i=1;i<=n;i++)
	{
		if(a[i]==mmax  &&  (a[i-1]!=a[i]  ||  a[i+1]!=a[i]))
		{
			printf("%d\n",i);
			return  ;
		}
	}
	printf("-1\n");
	return  ;
}
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)solve();
	return  0;
}

同机房奆佬还想了一个类似栈的做法,不再赘述。虽然刚开始思路错了,但是后来发现改一下就能规避问题了。

D

题意:
每个点都有一个权值,然后让你用n−1n-1n1条边把nnn个点连成一棵树,且要求边两端的点权值不能相同。

记录第一个点的颜色,找到第一个和第一个点不同颜色的点,记为ididid,如果一个点和第一个点不同颜色,则连向第一个点,如果和第一个点相同,则连向ididid

无解情况就是找不到ididid的情况。

#include<cstdio>
#include<cstring>
#define  N  5100
using  namespace  std;
int  co[N],n;
void  solve()
{
	scanf("%d",&n);
	for(int  i=1;i<=n;i++)
	{
		scanf("%d",&co[i]);
	}
	int  shit=co[1];
	int  id=0;
	for(int  i=2;i<=n;i++)
	{
		if(co[i]!=shit)
		{
			id=i;
			break;
		}
	}
	if(!id)
	{
		printf("NO\n");
		return  ;
	}
	printf("YES\n");
	for(int  i=2;i<=n;i++)
	{
		if(co[i]!=shit)printf("1 %d\n",i);
		else  printf("%d %d\n",id,i);
	}
}
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)solve();
	return  0;
 } 

E

题意:
赛后才知道题意
首先,轮舞是什么?
篝火晚宴见过吧,手拉着手一个圈跳舞就是轮舞,现在把n(n是偶数)n(n是偶数)n(n)个人(第iii个人编号为iii)等分成两个轮舞。
记为一种轮舞方案{A,BA,BA,B}。
两个轮舞X,YX,YX,Y相同,当且仅当选定某个点为开头时,从左往右形成的序列相同(也就是说[1,2,3][1,2,3][1,2,3][3,2,1][3,2,1][3,2,1]是不同的)。
轮舞方案{A1,B1A_{1},B_1A1,B1}和{A2,B2A_{2},B_2A2,B2}相同,当且仅当A1=A2,B1=B2A_1=A_2,B_1=B_2A1=A2,B1=B2或者A1=B2,B1=A2A_1=B_2,B_1=A_2A1=B2,B1=A2
问有多少个不同的方案,答案保证在longlonglong longlonglong范围内。

做法:
n=2mn=2mn=2m,先选mmm个人组成左边的圈,即CnmC_{n}^mCnm,但是选出了mmm个人,不同的圈排列个数是多少呢?我们不妨像题目中所说的,固定一个人就是在一号位置,其余人全排列,那么就是(n−1)!(n-1)!(n1)!,不难发现这样不会重复且可以找到所有的方案(显然正确因为每个人一定在圈上),然后就是Cnm∗(m−1)!∗(m−1)!C_{n}^{m}*(m-1)!*(m-1)!Cnm(m1)!(m1)!,但是发现一个圈方案左边右边都会被选上,所以还要除222(其实有规避的方法,假设编号为111的人一定被选上,那么就是Cn−1m−1C_{n-1}^{m-1}Cn1m1)。

化一下式子:
Cnm∗(m−1)!∗(m−1)!2=n!∗(m−1)!∗(m−1)!2∗m!∗m!=2n!n2=2(n−1)!n\frac{C_{n}^{m}*(m-1)!*(m-1)!}{2}=\frac{n!*(m-1)!*(m-1)!}{2*m!*m!}=\frac{2n!}{n^2}=\frac{2(n-1)!}{n}2Cnm(m1)!(m1)!=2m!m!n!(m1)!(m1)!=n22n!=n2(n1)!

这个式子就简单了很多。这个就是我考场上发现的规律。

打开计算器,发现19!19!19!不会爆longlonglong longlonglong,直接乱搞。

#include<cstdio>
#include<cstring>
using  namespace  std;
int  main()
{
	int  n;scanf("%d",&n);
	if(n==2)printf("1\n");
	else
	{
		long  long  ans=1;
		for(int  i=1;i<n;i++)ans*=i;
		ans/=n/2;
		printf("%lld\n",ans);
	}
	return  0;
}

F

一个n∗mn*mnm的矩阵,每一行最多能选m2\frac{m}{2}2m个,然后要求选出来数的总和是kkk的倍数,问总和的最大值是多少。

f[i][j][k]f[i][j][k]f[i][j][k]表示每一行的第iii个数字,选了jjj个,余数为kkk的最大值,不难想到状态转移方程。

时间复杂度:O(n4)O(n^4)O(n4)

#include<cstdio>
#include<cstring>
#define  N  90
using  namespace  std;
inline  int  mymax(int  x,int  y){return  x>y?x:y;}
int  a[N][N];
int  f[2][N][N];
int  n,m,K;
void  dp()
{
	memset(f[0],-20,sizeof(f[0]));
	f[0][0][0]=0;int  now=0,pre=1;
	int  limit=m/2;
	for(int  i=1;i<=n;i++)
	{
		now^=1;pre^=1;
		memset(f[now],-20,sizeof(f[now]));
		for(int  j=0;j<=limit;j++)
		{
			for(int  k=0;k<K;k++)f[now][0][k]=mymax(f[now][0][k],f[pre][j][k]);
		}
		for(int  j=1;j<=m;j++)
		{
			now^=1;pre^=1;
			memset(f[now],-20,sizeof(f[now]));
			for(int  k=1;k<=limit;k++)
			{
				for(int  t=0;t<K;t++)
				{
					int  shit=(t+a[i][j])%K;
					f[now][k][shit]=mymax(f[now][k][shit],f[pre][k-1][t]+a[i][j]);
				}
			}
			for(int  k=0;k<=limit;k++)
			{
				for(int  t=0;t<K;t++)f[now][k][t]=mymax(f[now][k][t],f[pre][k][t]);
			}
		}
	}
	now^=1;pre^=1;
	memset(f[now],-20,sizeof(f[now]));
	for(int  j=0;j<=limit;j++)
	{
		for(int  k=0;k<K;k++)f[now][0][k]=mymax(f[now][0][k],f[pre][j][k]);
	}
	printf("%d\n",f[now][0][0]);
}
int  main()
{
	scanf("%d%d%d",&n,&m,&K);
	for(int  i=1;i<=n;i++)
	{
		for(int  j=1;j<=m;j++)scanf("%d",&a[i][j]);
	}
	dp();
	return  0;
}

G

题意:
一个nnn个点mmm条边的无向图,有kkk个订单,每个订单是从xxx跑到yyy,定义每个订单的费用为xxxyyy的最短路,总费用为每个订单的费用和。

你最多可以让一条边变成000,然后问你最小总费用是多少。

做法:
对每个点跑一遍最短路,因为是稀疏图,SPFASPFASPFA很快。为了不被hack,后面又打了一个Dij的版本

肯定用掉机会比不用最小费用更小一点(最多不变)。

枚举是哪条边变成了000,对于订单x−>yx->yx>y,有两种操作,一种走原来的路,一种为了走000边而强行改变路线(可能没改),至于怎么让x−>yx->yx>y强行走一条边,大家也是懂的啦,直接把xxx走到边的一端的费用再加上另一端走到yyy的费用即可。

Dij时间复杂度:O(nmlog⁡m+mk)O(nm\log{m}+mk)O(nmlogm+mk)
SPFA:

#include<cstdio>
#include<cstring>
#define  N  1100
#define  M  2100
using  namespace  std;
typedef  long  long  LL;
inline  LL  mymin(LL  x,LL  y){return  x<y?x:y;}
LL  d[N][N];
int  list[N],head,tail,n,m,k;
bool  v[N];
struct  node
{
	int  y,next;
	LL  c;
}a[M];int  len,last[N];
inline  void  ins(int  x,int  y,LL  c){len++;a[len].y=y;a[len].c=c;a[len].next=last[x];last[x]=len;} 
void  SPFA(int  st)
{
	memset(d[st],20,sizeof(d[st]));d[st][st]=0;
	list[head=1]=st;tail=2;v[st]=1;
	while(head!=tail)
	{
		int  x=list[head++];if(head==n+1)head=1;
		v[x]=0;
		for(int  k=last[x];k;k=a[k].next)
		{
			int  y=a[k].y;
			if(d[st][x]+a[k].c<d[st][y])
			{
				d[st][y]=d[st][x]+a[k].c;
				if(!v[y])
				{
					v[y]=1;
					list[tail++]=y;if(tail==n+1)tail=1;
				}
			}
		}
	}
}
struct  SHIT
{
	int  x,y;
	SHIT(int  xx=0,int  yy=0){x=xx;y=yy;}
}zjj1[N],zjj2[N];
int  main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int  i=1;i<=m;i++)
	{
		int  x,y;LL  c;scanf("%d%d%lld",&x,&y,&c);
		ins(x,y,c);ins(y,x,c);
		zjj2[i]=SHIT(x,y);
	}
	for(int  i=1;i<=k;i++)
	{
		int  x,y;
		scanf("%d%d",&x,&y);
		zjj1[i]=SHIT(x,y);
	}
	for(int  i=1;i<=n;i++)SPFA(i);
	LL  ans=(LL)99999999999999;
	for(int  i=1;i<=m;i++)
	{
		LL  sum=0;
		int  x=zjj2[i].x,y=zjj2[i].y;
		for(int  j=1;j<=k;j++)
		{
			int  ax=zjj1[j].x,ay=zjj1[j].y;
			sum+=mymin(mymin(d[ax][x]+d[y][ay],d[ax][y]+d[x][ay]),d[ax][ay]);
		}
		ans=mymin(ans,sum);
	}
	printf("%lld\n",ans);
	return  0;
}

Dij:

#include<cstdio>
#include<cstring>
#include<queue>
#define  N  1100
#define  M  2100
using  namespace  std;
typedef  long  long  LL;
inline  LL  mymin(LL  x,LL  y){return  x<y?x:y;}
LL  d[N][N];
int  n,m,k;
bool  v[N];
struct  node
{
	int  y,next;
	LL  c;
}a[M];int  len,last[N];
inline  void  ins(int  x,int  y,LL  c){len++;a[len].y=y;a[len].c=c;a[len].next=last[x];last[x]=len;}
priority_queue<pair<LL,int>,vector<pair<LL,int> >,greater<pair<LL,int> > > fuck;
void  SPFA(int  st)
{
	memset(d[st],20,sizeof(d[st]));d[st][st]=0;
	memset(v,0,sizeof(v));
	while(!fuck.empty())fuck.pop();
	fuck.push(make_pair(0,st));
	for(int  i=1;i<=n;i++)
	{
		pair<LL,int>  zwq=fuck.top();fuck.pop();
		int  x=zwq.second;
		while(v[x])
		{
			zwq=fuck.top();fuck.pop();
			x=zwq.second;
		}
		v[x]=1;
		for(int  k=last[x];k;k=a[k].next)
		{
			int  y=a[k].y;
			if(!v[y]  &&  zwq.first+a[k].c<d[st][y])
			{
				d[st][y]=zwq.first+a[k].c;
				fuck.push(make_pair(d[st][y],y));
			}
		}
	}
}
struct  SHIT
{
	int  x,y;
	SHIT(int  xx=0,int  yy=0){x=xx;y=yy;}
}zjj1[N],zjj2[N];
int  main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int  i=1;i<=m;i++)
	{
		int  x,y;LL  c;scanf("%d%d%lld",&x,&y,&c);
		ins(x,y,c);ins(y,x,c);
		zjj2[i]=SHIT(x,y);
	}
	for(int  i=1;i<=k;i++)
	{
		int  x,y;
		scanf("%d%d",&x,&y);
		zjj1[i]=SHIT(x,y);
	}
	for(int  i=1;i<=n;i++)SPFA(i);
	LL  ans=(LL)99999999999999;
	for(int  i=1;i<=m;i++)
	{
		LL  sum=0;
		int  x=zjj2[i].x,y=zjj2[i].y;
		for(int  j=1;j<=k;j++)
		{
			int  ax=zjj1[j].x,ay=zjj1[j].y;
			sum+=mymin(mymin(d[ax][x]+d[y][ay],d[ax][y]+d[x][ay]),d[ax][ay]);
		}
		ans=mymin(ans,sum);
	}
	printf("%lld\n",ans);
	return  0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值