CF Round #679 div2赛后总结

前言

好不容易遇到一次简单的div2,竟然才A了三题,可恶的第4题,死活调不出来QAQ。

比赛地址

A

题意:给你 T T T组数据,每组数据 n n n个整数( n n n是偶数),分别为 a 1 , a 2 , . . . , a n a_{1},a_2,...,a_n a1,a2,...,an,每个数字的绝对值都小于等于 100 100 100且不为 0 0 0

现在让你求一个长度为 n n n b b b数组,满足每个数字是整数、绝对值都小于等于 100 100 100且不为 0 0 0

题解:很简单啊,对于每个相邻的数字这样处理就行了: a [ 1 ] ∗ a [ 2 ] + ( − a [ 1 ] ) ∗ a [ 2 ] = 0 a[1]*a[2]+(-a[1])*a[2]=0 a[1]a[2]+(a[1])a[2]=0,所以 b [ 1 ] = a [ 2 ] , b [ 2 ] = − a [ 1 ] b[1]=a[2],b[2]=-a[1] b[1]=a[2],b[2]=a[1],其余类似处理即可。

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

非常SB的我还想了几分钟

#include<cstdio>
#include<cstring>
using  namespace  std;
int  n,a[110];
int  main()
{
	int  T;scanf("%d",&T);
	for(int  i=1;i<=T;i++)
	{
		scanf("%d",&n);
		for(int  i=1;i<=n;i++)scanf("%d",&a[i]);
		for(int  i=1;i<=n;i+=2)
		{
			int  y=i+1;
			printf("%d %d ",-a[y],a[i]);
		}
		printf("\n");
	}
	return  0;
}

B

题意:T组数据,每组数据有个 n , m n,m n,m,表示 n m nm nm的矩阵(满足每组数据的 n , m n,m n,m加起来小于等于 250000 250000 250000),然后序号为 1 1 1~ n m nm nm的点在这个矩阵中,然后其会给你每一行从左到右的点的编号,和每一列从上到下的点的编号,但是行与行、列与列之间的相对位置不一定是对的,现在要求你还原这个矩阵。

题解:非常的简单,只要找到包含每一行第一个数字的列,就能得到行的相对位置,直接输出即可,时间复杂度可以到: O ( n m ) O(nm) O(nm),但是为了偷懒,我用排序快速的打出了 O ( n m log ⁡ n m ) O(nm\log{nm}) O(nmlognm)的打法,虽然慢,但是打的快。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define  N  510
#define  NN  260000
using  namespace  std;
struct  node
{
	int  a[N];
}a[N];
int  id[NN];bool  v[NN];
inline  bool  cmp(node  x,node  y){return  id[x.a[1]]<id[y.a[1]];}
int  n,m;
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		for(int  i=1;i<=n;i++)
		{
			for(int  j=1;j<=m;j++)scanf("%d",&a[i].a[j]);
			v[a[i].a[1]]=1;
		}
		for(int  i=1;i<=m;i++)
		{
			int  x=0;
			for(int  j=1;j<=n;j++)
			{
				scanf("%d",&x);
				if(v[x]==1)id[x]=j;
			}
		}
		sort(a+1,a+n+1,cmp);
		for(int  i=1;i<=n;i++)
		{
			for(int  j=1;j<=m;j++)printf("%d ",a[i].a[j]);
			printf("\n");
		}
		for(int  i=1;i<=n;i++)v[a[i].a[1]]=0;
	}
	return  0;
}

C

题意:现在有 6 6 6个正整数的 a a a数组,还有 n n n个正整数的 b b b数组(对于任意的 1 ≤ i ≤ n , 1 ≤ j ≤ 6 1≤i≤n,1≤j≤6 1in,1j6,满足 b i > a j b_{i}>a_{j} bi>aj),然后要求现在构造一个 c c c数组,对于 c i c_{i} ci,其等于 b i − a j b_{i}-a{j} biaj j j j是自己定的),然后 c c c数组的权值为最大的数字减去最小的数字,求最小的权值。

题解:我们不妨考虑暴力枚举 l l l虽然是1e9的级别,然后看看其对应的 r r r最小能是多少,这个应该怎么维护呢?也就是说 [ l , r ] [l,r] [l,r]中必须能包含一个 c c c数组。

我们不妨用一个数字把每一个 b i − a j b_{i}-a_{j} biaj保存起来,总共 6 n 6n 6n个数字,从小到大排序,然后对于 l + + l++ l++,我们只要把所有 b i − a j < l b_{i}-a_{j}<l biaj<l删掉,然后找到另外一个最小的 b i − a k ≥ l b_{i}-a_{k}≥l biakl加入进去即可,然后 r r r m a x max max

但是 l l l移动 1 e 9 1e9 1e9次的问题还有解决,我们发现, l l l只有移动到 6 n 6n 6n个数字才是有用的,于是优化一下, l l l就只用跳 6 n 6n 6n次了,而每个数字最多被删除一次,也是 6 n 6n 6n次,所以就是 O ( n l o g n ) O(nlogn) O(nlogn)。(实际上用基排可以到 O ( n ) O(n) O(n)

#include<cstdio>
#include<cstring>
#include<algorithm>
#define  N  110000
#define  NN  610000
using  namespace  std;
inline  int  mymax(int  x,int  y){return  x>y?x:y;}
inline  int  mymin(int  x,int  y){return  x<y?x:y;}
int  a[10],b[N],n;
struct  node
{
	int  x/*数字*/,y/*对应的哪个b[y]*/,next/*下一个b[y]-a[k]*/;
}dp[NN];int  las[N],len;
inline  bool  cmp(node  x,node  y){return  x.x<y.x;}
inline  bool  cmp2(int  x,int  y){return  x>y;}
int  main()
{
	memset(las,0,sizeof(las));
	for(int  i=1;i<=6;i++)scanf("%d",&a[i]);
	sort(a+1,a+7,cmp2);//其实没有必要
	
	scanf("%d",&n);
	int  l,r=1;
	for(int  i=1;i<=n;i++)
	{
		scanf("%d",&b[i]);
		r=mymax(r,b[i]-a[1]);
		for(int  j=1;j<=6;j++)
		{
			len++;
			dp[len].x=b[i]-a[j];
			dp[len].y=i;
		}
	}
	sort(dp+1,dp+len+1,cmp);
	
	for(int  i=len;i>=1;i--)//处理next
	{
		dp[i].next=las[dp[i].y];
		las[dp[i].y]=i;
	}
	l=dp[1].x;int  ans=1000000000;
	for(int  i=1;i<=len;)
	{
		ans=mymin(r-l,ans);
		while(i<=len  &&  dp[i].x==l)//不断的删除数字
		{
			if(!dp[i].next)//已经没有数字了
			{
				printf("%d\n",ans);
				return  0;
			}
			r=mymax(dp[dp[i].next].x,r);
			i++;
		}
		l=dp[i].x;
	}
	printf("%d\n",ans);
	return  0;
}

D

题意:一个人要卖 1 1 1~ n n n价格的物品(每个物品各一个),每个时间点有两种操作:

  1. 其放上一个物品,价格不知道。
  2. 一个人来买走最小价格的物品。

现在给你 2 n 2n 2n个时间点的操作, + + +表示放上物品, − - x x x表示一个人来买走了 x x x价格的物品,现在要求你构造出一个满足要求的放物品序列,没有输出 N O NO NO

题解:设 a [ i ] a[i] a[i]为第 i i i次买走是什么物品, i d [ i ] id[i] id[i]满足 ∀ j ∈ [ i d [ i ] , i − 1 ] , a [ j ] < a [ i ] ∀j∈[id[i],i-1],a[j]<a[i] j[id[i],i1],a[j]<a[i],且要求 i d [ i ] id[i] id[i]是最小的,而对于每个 + + +号,其隶属于后面第一个 − - 号,很明显, a [ i ] a[i] a[i]只要放在隶属于 [ i d [ i ] , i ] [id[i],i] [id[i],i]中的任意一个 + + +号,而且不难发现, a [ i ] a[i] a[i]放在 [ 1 , i d [ i ] − 1 ] [1,id[i]-1] [1,id[i]1]的位置会导致 i d [ i ] − 1 id[i]-1 id[i]1错误,所以 a [ i ] a[i] a[i]只能且任意放在隶属于 [ i d [ i ] , i ] [id[i],i] [id[i],i]中的任意一个 + + +号。

当然,不难发现,对于每个数字,在前面已经放完之后,尽量的往前方就行了,用并查集维护。(往后放可能会导致后面的数字放不了)

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

#include<cstdio>
#include<cstring>
#define  N  110000
#define  NN  210000
using  namespace  std;
int  fa[N];
int  findfa(int  x)
{
	if(fa[x]!=x)fa[x]=findfa(fa[x]);
	return  fa[x];
}
int  ll[N],rr[N],a[N];
int  id[N],n;
int  sta[N],top;
inline  int  erfen(int  x)
{
	int  l=1,r=top,ans=x,mid;
	while(l<=r)
	{
		mid=(l+r)/2;
		if(a[sta[mid]]<a[x])r=mid-1,ans=sta[mid-1]+1;
		else  l=mid+1;
	}
	return  ans;
}
int  lis[N]; 
int  main()
{
//	freopen("std.in","r",stdin);
//	freopen("std.out","w",stdout);
	scanf("%d",&n);
	int  ed=2*n;
	int  l1=0,l2=0,pre=1;
	for(int  i=1;i<=ed;i++)
	{
		char  st[10];
		scanf("%s",st+1);
		if(st[1]=='-')
		{
			l1++;scanf("%d",&a[l1]);
			ll[l1]=pre;rr[l1]=l2;pre=l2+1;
			fa[l1]=l1;
		}
		else  l2++;
	}
	fa[n+1]=n+1;
	
	id[1]=1;sta[top=1]=1;
	for(int  i=2;i<=n;i++)
	{
		id[i]=erfen(i);
		while(top  &&  a[sta[top]]<a[i])top--;
		sta[++top]=i;
	}
	
	for(int  i=1;i<=n;i++)
	{
		int  x=findfa(ll[id[i]]);
		if(x>rr[i])
		{
			printf("NO\n");
			return  0;
		}
		else
		{
			lis[x]=a[i];
			fa[x]=x+1;
		}
	}
	printf("YES\n");
	for(int  i=1;i<=n;i++)printf("%d ",lis[i]);
	printf("\n");
	return  0;
}

事实上,二分部分可以跟单调栈的弹出合并到一起,并查急可以优化到 O ( n α ( n ) ) O(nα(n)) O(nα(n))

所以可以到达 O ( n α ( n ) ) O(nα(n)) O(nα(n))

当然,还有严格 O ( n ) O(n) O(n)的做法,不难发现,我们的瓶颈在于往后放可能会导致后面的数字放不了,但是我们发现,如果 a [ j ] < a [ j ] ( i < j ) a[j]<a[j](i<j) a[j]<a[j](i<j),那么 a [ i ] a[i] a[i]能放到的地方 a [ j ] a[j] a[j]也能放到,所以 a [ i ] a[i] a[i]往后放,而不是往前放的话,并不会影响 a [ j ] a[j] a[j]放置,如果 a [ j ] > a [ i ] a[j]>a[i] a[j]>a[i] a [ i ] a[i] a[i]压根就放不到 a [ j ] a[j] a[j]能放的位置, a [ j ] a[j] a[j]不就随便放了吗?

然后用单调栈随便维护一下就可以了。

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

#include<cstdio>
#include<cstring> 
#define  N  110000
#define  NN  210000
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  sta[NN],top;
int  n,lis[N];
int  main()
{
	scanf("%d",&n);
	int  ed=2*n,l1=0;
	for(int  i=1;i<=ed;i++)
	{
		char  st[10];scanf("%s",st);
		if(st[0]=='+')l1++,sta[++top]=-l1;
		else
		{
			int  x=0;scanf("%d",&x);
			bool  bk=0;
			while(top)
			{
				if(sta[top]>0  &&  sta[top]<x)top--;
				else  if(sta[top]<0)
				{
					lis[-sta[top]]=x;
					top--;
					bk=1;
					break;
				}
				else  break;
			}
			if(!bk)
			{
				printf("NO\n");
				return  0;
			}
			sta[++top]=x;
		}
	}
	printf("YES\n");
	for(int  i=1;i<=n;i++)printf("%d ",lis[i]);
	printf("\n");
	return  0;
}

E

题意:有一个魔法,在 t t t秒施法时瞬间打掉怪物 a a a点血量,然后在 t + 1 , t + 2 , . . . , t + c t+1,t+2,...,t+c t+1,t+2,...,t+c的时间点回复 b b b点血量,施法有 d d d c d cd cd,相当于 t t t秒施法后, t + d t+d t+d才能再次施法。

回血效果可以叠加,如果一个时间点有多个血量变化,同时计算,然后问你最多可以打掉多少血的怪物,无限的话输出 − 1 -1 1

T T T组数据,每组给定魔法 a , b , c , d a,b,c,d a,b,c,d

做法:不难发现, a > b c a>bc a>bc的话,就是 − 1 -1 1,反之,不能打败无限血的怪物,那么很明显, t t t时刻放完魔法后, t + d t+d t+d时刻立马放很明显更加优秀。(你总不可能等他多回一点血再打吧。)

所以放魔法的时间就是: 1 , 1 + d , 1 + 2 d , 1 + 3 d . . . 1,1+d,1+2d,1+3d... 1,1+d,1+2d,1+3d...,那么什么时候达到最大值呢?

考虑回本时间,回本时间就是指 1 1 1时刻放完魔法后,在哪个时刻第一次的攻击血量会被其回血血量会上来,不难发现,会本时间就是: ( b − 1 ) / a + 2 (b-1)/a+2 (b1)/a+2,设为 t i m tim tim

先证明 ≥ t i m ≥tim tim再放魔法的话肯定不是最大值:
对于 k ≥ t i m k≥tim ktim放完魔法,此时 1 1 1时刻的攻击已经被完全的消除了(甚至可能回得更多),那么不妨构造新的方案,在 1 1 1时刻不攻击, 1 + d 1+d 1+d时刻为真正的 1 1 1时刻,此时最大的攻击血量一定不小于原来的方案。

再证明 < t i m <tim <tim放魔法一定会更大:
类似的证明方法,在 k < t i m k<tim k<tim的位置实施魔法,不妨证明其比 1 , 1 + d , . . . , k − d 1,1+d,...,k-d 1,1+d,...,kd的方案更加优秀,类似的证明,构造新方案, 1 1 1不放魔法, 1 + d 1+d 1+d为名正言顺的 1 1 1时刻,那么新方案的血量等于 1 , 1 + d , . . . , k − d 1,1+d,...,k-d 1,1+d,...,kd的方案,但是原方案比新方案多了个 1 1 1,且其并未回本,所以原方案 > > >新方案 = = = 1 , 1 + d , . . . , k − d 1,1+d,...,k-d 1,1+d,...,kd的方案。

然后推推式子就行了。

单次复杂度: O ( 1 ) O(1) O(1)

#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  LL;
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)
	{
		LL  a,b,c,d;scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
		if(a>b*c)printf("-1\n");
		else
		{
			LL  tim=(a-1)/b+1;//回本时间-1
			LL  kao=(tim-1)/d+1;
			
			LL  ans=kao*a-(d*b)*kao*(kao-1)/2;
			printf("%lld\n",ans);
		}
	}
	return  0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值