CF# div.2赛后总结

文章目录

前言

比赛链接

ZWQking AK啦!!!!!!!Orz

A

题意:有 n n n个小孩,有 4 n 4n 4n个位置,要求你安排小孩坐位置,使得被坐的位置的编号 a , b a,b a,b满足: g c d ( a , b ) ≠ 1 , a , b gcd(a,b)≠1,a,b gcd(a,b)=1,a,b

做法:构造法,让他们坐 2 n + 2 , 2 n + 4 , 2 n + 6 , . . . , 4 n 2n+2,2n+4,2n+6,...,4n 2n+2,2n+4,2n+6,...,4n的位置即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using  namespace  std;
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)
	{
		int  n;scanf("%d",&n);
		int  limit=4*n,ed=2*n;
		for(int  i=limit;i>ed;i-=2)printf("%d ",i);
		printf("\n");
	}
	return  0;
}

B

题意:给你一个字符串, 1 1 1的位置有地雷, 0 0 0没有,你可以花 a a a代价引爆连续一段的雷,或者花 b b b代价埋一颗雷。

做法:

  1. DP做法, d p [ i ] [ 0 / 1 ] dp[i][0/1] dp[i][0/1]分别表示这个位置没有地雷和有地雷的转移。
  2. 赛后想了想,可以贪心,计算中间的每一段连续的 0 0 0填满的代价,如果小于 a a a则填满,否则不填满,直接两端引爆。

我才用的是 D P DP DP做法。

两种做法时间复杂度都是: O ( n ) O(n) O(n)

#include<cstdio>
#include<cstring>
#define  N  110000
using  namespace  std;
inline  int  mymin(int  x,int  y){return  x<y?x:y;}
inline  int  mymax(int  x,int  y){return  x>y?x:y;}
int  dp[N][2];
char  st[N];
int  n,a,b;
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&a,&b);
		scanf("%s",st+2);
		n=strlen(st+2)+2;
		st[1]=st[n]='0';
		for(int  i=1;i<=n;i++)dp[i][0]=dp[i][1]=999999999;
		dp[1][0]=0;
		for(int  i=2;i<=n;i++)
		{
			if(st[i]=='0')
			{
				dp[i][0]=mymin(dp[i-1][0],dp[i-1][1]+a);
				dp[i][1]=mymin(dp[i-1][0],dp[i-1][1])+b;
			}
			else  dp[i][1]=mymin(dp[i-1][0],dp[i-1][1]);
		}
		printf("%d\n",dp[n][0]);
	}
	return  0;
}

C

题意: 给你 a a a数组和 b b b数组,对于每个 i i i,要么让小明多花 b i b_{i} bi的时间,要么直接多出一个人花 a i a_{i} ai的时间。

然后问你如何安排才可以让花费时间最大的人最小。

做法:二分答案,还算简单。

#include<cstdio>
#include<cstring>
#define  N  210000
using  namespace  std;
inline  int  mymax(int  x,int  y){return  x>y?x:y;}
int  a[N],b[N],n;
inline  bool  check(int  k)
{
	int  shit=0;
	for(int  i=1;i<=n;i++)
	{
		if(a[i]>k)
		{
			shit+=b[i];
			if(shit>k)return  0;
		}
	}
	return  1;
}
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		int  l=1,r=0;
		for(int  i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			r=mymax(r,a[i]);
		}
		for(int  i=1;i<=n;i++)scanf("%d",&b[i]);
		int  mid,ans=r;
		while(l<=r)
		{
			mid=(l+r)>>1;
			if(check(mid)==1)r=mid-1,ans=mid;
			else  l=mid+1;
		}
		printf("%d\n",ans);
	}
	return  0;
}

D

题意: 给你一个 a a a数组,你有两种操作:

  1. 选择一个 i i i,让 1 1 1~ i i i的位置全部减一。
  2. 选择一个 i i i,让 i i i~ n n n的位置全部减一。
    问是否可以把 a a a数组全部变成 0 0 0

做法:他们都说很简单,就我想了挺久的。

我们不妨处理 1 1 1操作,设 b b b数组, b i b_{i} bi表示第 i i i个位置经过 1 1 1操作减去了 b i b_{i} bi

b i ≥ b i + 1 b_{i}≥b_{i+1} bibi+1

这样,只需要构造一个合法的 b b b数组使得 a a a数组减完 b b b数组后从右往左到最后一个非 0 0 0数字非严格单调递减。

然后就开始考虑差分约束, b i ≥ b i + 1 b_{i}≥b_{i+1} bibi+1 i + 1 i+1 i+1 i i i连一条 0 0 0的边,因为要 a i − b i ≤ a i + 1 − b i + 1 a_{i}-b_{i}≤a_{i+1}-b_{i+1} aibiai+1bi+1,所以 a i − a i + 1 + b i + 1 ≤ b i a_{i}-a_{i+1}+b_{i+1}≤b_{i} aiai+1+bi+1bi i + 1 i+1 i+1 i i i连接一条边权 a i − a i + 1 a_{i}-a_{i+1} aiai+1的边,从 n n n点开始跑最长路,然后最后检查 b i ≤ a i b_{i}≤a_{i} biai即可,但是后面发现了一个事情,边只会从 i + 1 i+1 i+1连向 i i i,直接一遍扫过去就行了啊(╯‵□′)╯︵┻━┻。

当然,你可以直接默认 b n b_{n} bn 0 0 0,因为如果 b n > 0 b_{n}>0 bn>0,完全可以把 1 1 1~ n n n 1 1 1操作拆成一个 1 1 1操作一个 2 2 2操作来搞,所以可以直接默认 b n = 0 b_{n}=0 bn=0

时间复杂度: O ( n ) O(n) O(n)

#include<cstdio>
#include<cstring>
#include<queue>
#define  N  31000
using  namespace  std;
int  dp[N],n,a[N];
inline  int  mymax(int  x,int  y){return  x>y?x:y;}
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		for(int  i=1;i<=n;i++)scanf("%d",&a[i]);
		bool  bk=0;
		dp[n]=0;
		for(int  i=n-1;i>=1;i--)
		{
			if(a[i]>a[i+1])dp[i]=dp[i+1]+a[i]-a[i+1];
			else  dp[i]=dp[i+1];
			if(dp[i]>a[i])
			{
				bk=1;
				break;
			}
		}
		if(!bk)printf("YES\n");
		else  printf("NO\n");
	}
	return  0;
}

E

题意:默认现在是字典序最小的全排列(即: 1 , 2 , 3 , 4 , . . . , n 1,2,3,4,...,n 1,2,3,4,...,n),长度为 n n n,然后有两个操作:

  1. 统计 [ l , r ] [l,r] [l,r]的区间和。
  2. 假设现在的全排列字典序排名为 x x x,给你一个 y y y,让你把全排列变成字典序排名为 x + y x+y x+y的全排列。

做法: 和康托展开非常有关系,因为全排列排名总和为 1 e 12 1e12 1e12,发现 16 ! 16! 16!已经大于这个数字,暴力维护后面 16 16 16个数字,然后暴力统计即可。

时间复杂度: O ( q 1 6 2 ) O(q16^2) O(q162)

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#define  N  210000
#define  NN  410000
using  namespace  std;
typedef  long  long  LL;
int  a[N],b[20],n,q;
LL  fc[20];
bool  v[N];
void  kangtuo(int  l,LL  k)
{
	int  top=0;
	for(int  i=l;i<=n;i++)b[++top]=i,v[top]=0;
	sort(b+1,b+top+1);
	for(int  i=top;i>=2;i--)
	{
		LL  shit=k/fc[i-1];k%=fc[i-1];
		for(int  j=1;j<=top;j++)
		{
			if(!shit  &&  !v[j])
			{
				a[top-i+l]=b[j];
				v[j]=1;
				break;
			}
			if(!v[j])shit--;
		}
	}
	for(int  i=1;i<=top;i++)if(!v[i])a[n]=b[i];
}
LL  nowcnt=0;
inline  LL  getsum(int  l,int  r){return  (LL)(l+r)*(r-l+1)/2;}
int  main()
{
	scanf("%d%d",&n,&q);
	fc[0]=1;for(int  i=1;i<=16;i++)fc[i]=fc[i-1]*(LL)i;
	for(int  i=1;i<=n;i++)a[i]=i;
	
	int  ll=n-16+1;
	if(ll<=0)ll=1;
	for(int  i=1;i<=q;i++)
	{
		int  type;scanf("%d",&type);
		if(type==1)
		{
			int  l,r;scanf("%d%d",&l,&r);
			if(r<ll)printf("%lld\n",getsum(l,r));
			else
			{
				LL  sum=0;
				int  lll=ll;
				if(l>=ll)lll=l;
				else  sum=getsum(l,ll-1);
				for(int  i=lll;i<=r;i++)sum+=a[i];
				printf("%lld\n",sum);
			}
		}
		else
		{
			int  x;scanf("%d",&x);
			nowcnt+=x;
			kangtuo(ll,nowcnt);
		}
	}
	return  0;
}

F

题意:给你一个 a a a数组,长度为 n n n,可以操作 k k k次,第 t t t次操作,你可以删掉第 i ( 1 ≤ t ≤ n − t + 1 ) i(1≤t≤n-t+1) i(1tnt+1)个数字,然后把 a i + 1 a_{i+1} ai+1或者 a i − 1 a_{i-1} ai1(必须满足被贴的数字有意义,即在数组范围内,且必须贴数字)贴到 b b b数组最右边( b b b数组一开始为空),然后把 a i a_{i} ai~ a n a_{n} an全部往左移一位,现在给你 a , b a,b a,b数组( a , b a,b a,b数组的数字都是不同的),问你操作序列能有多少个。

做法: 首先化一下题意: b b b数组在 a a a数组中对应的位置被锁上了,也就是不能被删除,然后对于 b 1 b_{1} b1,其在 a a a数组对应的位置为 a i a_{i} ai,如果 i + 1 , i − 1 i+1,i-1 i+1,i1的位置都被锁上了,这个 b b b数组绝对得不到,否则,假设 i − 1 i-1 i1解锁了,相当于牺牲掉 i − 1 i-1 i1的位置给 i i i位置解锁。

在上述过程中,不难发现,如果对于一个上锁的位置 i i i,左边如果没有被锁,或者解锁的时间早于它,那么在解锁 i i i时,左边一定是未锁上的位置(因为如果左边牺牲自己去解锁更左边的位置,那么更左边就变成了未锁上的左边),右边同理。

在这里插入图片描述
橙色为锁上的位置,红色为未锁上的位置,蓝色代表牺牲。

这样,就只需要在开始的时候判断每个位置是否可以牺牲左边或者右边即可。

时间复杂度: O ( n ) O(n) O(n)

#include<cstdio>
#include<cstring>
#define  N  210000
using  namespace  std;
typedef  long  long  LL;
const  LL  mod=998244353;
int  a[N],b[N],c[N],n,k;
inline  LL  ksm(LL  x,LL  y)
{
	LL  ans=1;
	for(LL  i=1;i<=y;i++)ans=ans*x%mod;
	return  ans; 
}
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&k);
		for(int  i=1;i<=n;i++){scanf("%d",&a[i]);c[i]=0;}
		int  cnt=0;LL  ans=0;
		for(int  i=1;i<=k;i++)
		{
			scanf("%d",&b[i]);
			c[b[i]]=i;
		}
		for(int  i=1;i<=n;i++)
		{
			if(c[a[i]]>0)
			{
				if((c[a[i]]<c[a[i+1]]  &&  c[a[i]]<c[a[i-1]])  ||  (i==1  &&  c[a[i+1]]>c[a[i]])  ||  (i==n  &&  c[a[i-1]]>c[a[i]]))
				{
					ans=-1;
					break;
				}
				else  if(i!=1  &&  i!=n  &&  c[a[i-1]]<c[a[i]]  &&  c[a[i+1]]<c[a[i]])cnt++;
			}
		}
		if(ans==-1)printf("0\n");
		else  printf("%lld\n",ksm(2,cnt));
	}
	return   0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值