CF #673 div2 赛后总结

文章目录

前言

完成成就:在学校熬夜熬到1点

第一次CF打比赛就炸成这个样子

A

题目翻译:

一个长度为 n n n的数组,每次选择 i , j ( 1 ≤ i , j ≤ n , i ≠ j ) i,j(1≤i,j≤n,i≠j) i,j(1i,jn,i=j),然后执行执行一个操作: a [ j ] = a [ i ] + a [ j ] a[j]=a[i]+a[j] a[j]=a[i]+a[j],并且要求操作完后 a [ j ] ≤ k a[j]≤k a[j]k,问最多进行多少次操作。

这不直接贪心搞?

很明显每次拿每一个去跟最小的数字copy一下即可,因为用最小的数字增长的最慢啊。

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

#include<cstdio>
#include<cstring>
#include<algorithm>
#define  N  1100
using  namespace  std;
int  a[N];
int  n,K;
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]);
		sort(a+1,a+n+1);
		int  ans=0;
		for(int  i=2;i<=n;i++)ans+=(K-a[i])/a[1];
		printf("%d\n",ans);
	}
	return  0;
}

因为懒得打找最小直接排序了

B

题目翻译:

有一个对于数组的估值函数 f f f f ( a ) f(a) f(a)是满足 i ≠ j , a [ i ] + a [ j ] = T i≠j,a[i]+a[j]=T i=j,a[i]+a[j]=T的二元组 ( i , j ) (i,j) (i,j)的数量,然后让你把 a a a数组分成 b , c b,c b,c两个数组,同时要求 f ( b ) + f ( c ) f(b)+f(c) f(b)+f(c)最小。

不难发现,对于一个数字 x x x T − x T-x Tx是固定的,且对于 T − x T-x Tx而言, x x x也是固定的,所以只需要把 x x x T − x T-x Tx分到两个不同的数组即可。

但是需要注意的是: x = T − x x=T-x x=Tx的情况时,需要均匀的分配。

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

#include<cstdio>
#include<cstring>
#include<map>
#define  N  110000
using  namespace  std;
map<int,int> fuck;//直接用map算了
int  a[N],n,m,ans[N];
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)
	{
		fuck.clear();
		scanf("%d%d",&n,&m);
		for(int  i=1;i<=n;i++)scanf("%d",&a[i]);
		for(int  i=1;i<=n;i++)
		{
			int  x=a[i];
			if(fuck.count(m-x)==1)ans[i]=fuck[m-x]^1;
			else  ans[i]=0;
			fuck[x]=ans[i];
		}
		for(int  i=1;i<=n;i++)printf("%d ",ans[i]);
		printf("\n");
	}
	return  0;
}

C

题目翻译:

对于一个长度为 n n n的数组,你需要对于 [ 1 , n ] [1,n] [1,n]中每个数字都找到其对应的神奇数:
(我们设现在正在进行寻找的数字是 k k k)对于每一段长度为 k k k的连续子段,如果这些子段中的最小值都是同一个数字,那么这个数字就是数字 k k k的神奇数,简称 k − k- k神奇数。

不难发现, k k k越大,答案只会越小,满足单调性,又发现 a a a数组的数字都在 [ 1 , n ] [1,n] [1,n]范围内,所以我们针对每一个数字,算出当子段长度大到什么数字时,答案小于等于这个数字,然后用个单调栈维护即可。

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

#include<cstdio>
#include<cstring>
#define  N  310000
using  namespace  std;
int  a[N],n;
int  last[N],minn[N];
int  sta[N],top;
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++)minn[i]=-1,last[i]=0;
		for(int  i=1;i<=n;i++)
		{
			int  x;scanf("%d",&x);
			int  val=i-last[x];
			minn[x]=mymax(minn[x],val);
			last[x]=i;
		}
		for(int  i=1;i<=n;i++)
		{
			if(last[i])minn[i]=mymax(n-last[i]+1,minn[i]);
		}
		top=0;
		for(int  i=n;i>=1;i--)
		{
			if(minn[i]==-1)continue;
			int  val=minn[i];
			while(top  &&  minn[sta[top]]>=val)top--;
			sta[++top]=i;
		}
		int  ll=0;
		for(int  i=1;i<=n;i++)
		{
			while(ll<top  &&  minn[sta[ll+1]]<=i)ll++;
			if(!ll)printf("-1 ");
			else  printf("%d ",sta[ll]);
		}
		printf("\n");
	}
	return  0;
}

D

题目翻译:

对于一个数组 a a a(里面的数字都是正数)。

你可以选择 i , j , x ( 1 ≤ i , j ≤ n , 0 ≤ x ≤ 1 0 9 ) i,j,x(1≤i,j≤n,0≤x≤10^9) i,j,x(1i,jn,0x109)并进行一次操作(其实 x x x小于等于 1 0 9 10^9 109没用的,反正数组总和也才这么大):
a i − = i ∗ x , a j + = i ∗ x a_i-=i*x,a_j+=i*x ai=ix,aj+=ix
并且要求每次操作完后的数组中的数字都是非负的,问要多少步才能让所有数字相同,并输出操作次数和操作序列,请注意,操作次数只要在 [ 0 , 3 n ] [0,3n] [0,3n]之间即可。

不难发现, 1 1 1号节点可以自由的让别人加数字,所以我们不妨考虑把其他的点多余平均数的部分汇聚到 1 1 1点。然后我WA了。

但是赛后我通过膜拜奆佬,发现了真正的做法:
也是汇聚到 1 1 1点,设平均数为 o v a ova ova,如果 o v a < a [ i ] < i ova<a[i]<i ova<a[i]<i,上述做法就会出问题,但是如果我们如果强行凑到 i i i,有可能会面临 1 1 1号节点的数字不够的问题。

因此我们需要把所有的数字汇聚到 1 1 1点,对于第 i i i号节点 ( i ≥ 2 ) (i≥2) (i2),如果 a [ i ] m o d    i ≠ 0 a[i]\mod i≠0 a[i]modi=0,那么我们只需要直接把缺的部分补满然后全部扔到 1 1 1i即可,因为缺的数字最多是 i − 1 i-1 i1个,同时因为原始数字的数字都是正数,所以 1 1 1在拿走了 [ 1 , i − 1 ] [1,i-1] [1,i1]中所有数字之后,肯定是大于等于 i − 1 i-1 i1个数字的,所以肯定可以拿走 i i i的所有数字。

我们发现,上述的操作方案最多会有 3 ( n − 1 ) 3(n-1) 3(n1)个操作。

#include<cstdio>
#include<cstring>
#define  N  15000
#define  NN  500005
using  namespace  std;
int  a[N],n,T;
struct  ANSWER
{
	int  i,j,t;
}ans[NN];int  top;
int  main()
{
	scanf("%d",&T);
	while(T--)
	{
		top=0;memset(ans,0,sizeof(ans));
		scanf("%d",&n);
		int  sum=0;
		for(int  i=1;i<=n;i++){scanf("%d",&a[i]);sum+=a[i];}
		if(sum%n!=0)
		{
			printf("-1\n");
			continue;
		}
		sum/=n;
		for(int  i=2;i<=n;i++)
		{
			int  val=i-a[i]%i;
			if(val!=i)
			{
				++top;
				ans[top].i=1;ans[top].j=i;ans[top].t=val;
				a[i]+=val;
			}
			++top;
			ans[top].i=i;ans[top].j=1;ans[top].t=a[i]/i;
		}
		for(int  i=2;i<=n;i++)
		{
			top++;
			ans[top].i=1;ans[top].j=i;ans[top].t=sum;
		printf("%d\n",top);
		for(int  i=1;i<=top;i++)printf("%d %d %d\n",ans[i].i,ans[i].j,ans[i].t);
	}
	return  0;
}

E

题目翻译:
给你个数组 a a a,要求你找到一个 x x x,使得操作后得到的数组 b b b的逆序对数最少。

操作法则为对于 a a a中每个数字与 x x x异或,得到 b b b

思路:
我们针对最高位先进行分类讨论。

如果都是 0 0 0的话,那么我们不难发现这些要判断逆序对数只能靠下一位,不管他。

1 1 1也是。

但是 0 , 1 0,1 0,1交替呢?我们先讨论这一位,我们统计一下这一位是 0 0 0时或者是 1 1 1时可以贡献的逆序对数。

然后讨论这一位是 0 / 1 0/1 0/1是对于下一位的影响,我们设现在的数组是 a a a,最高位是 1 1 1的数字的数组是 a 1 a1 a1,是 0 0 0的数组是 a 0 a0 a0,我们发现,不管这一位是 0 0 0还是 1 1 1,在下一位中 a 0 , a 1 a0,a1 a0,a1都是独立的产生贡献的(因为交叉产生贡献在这一位就处理了),所以我们只要在一位一位处理并不断分裂即可。

至于例子,拿样例手操一下就知道了。

考试我没有开long long!!!

#include<cstdio>
#include<cstring>
#define  N  610000
using  namespace  std;
typedef  long  long  LL;
int  aa[2][N],n,shit,mid;
LL  to1,to2;
inline  void  chuli(int  *a,int  top,int  *b)
{
	int  zt=0,ot=0;
	for(int  i=top;i>=1;i--)
	{
		if(a[i]&shit)ot++;
		else  zt++;
	}
	int  zc=0,oc=0;
	for(int  i=top;i>=1;i--)
	{
		if(a[i]&shit)
		{
			oc++;b[top-oc+1]=a[i];
			to2+=zc;
		}
		else
		{
			zc++;b[zt-zc+1]=a[i];
			to1+=oc;
		}
	}
	mid=zt;
}
int  ll[2][N],rr[2][N],top[2];
int  main()
{
	long  long  ans2=0,ans1=0;
	scanf("%d",&n);
	for(int  i=1;i<=n;i++)scanf("%d",&aa[0][i]);
	top[0]=1;ll[0][1]=1;rr[0][1]=n;
	int  pre=1,now=0;
	for(int  i=30;i>=1;i--)
	{
		now^=1;pre^=1;
		shit=(1<<(i-1));
		
		top[now]=0;
		to1=to2=0;
		for(int  i=1;i<=top[pre];i++)
		{
			chuli(aa[pre]+ll[pre][i]-1,rr[pre][i]-ll[pre][i]+1,aa[now]+ll[pre][i]-1);
			if(mid>0)
			{
				int  x=++top[now];
				ll[now][x]=ll[pre][i];
				rr[now][x]=ll[pre][i]+mid-1;
			}
			if(mid!=rr[pre][i]-ll[pre][i]+1)
			{
				int  x=++top[now];
				ll[now][x]=ll[pre][i]+mid;
				rr[now][x]=rr[pre][i];
			}
		}
		if(to1>=to2)ans2+=to2;
		else  ans2+=to1,ans1^=shit;
	}
	printf("%lld %lld\n",ans2,ans1);
	return  0;
}

F

题目翻译:

一开始有 n n n个点, m m m条边, q q q个询问,然后接下来有 q q q个询问(有两种):

  1. 对于 1 1 1号询问,是询问 x x x所能到达的点(包括自己)的 p p p的最大值,然后输出,并且把 p m a x p_{max} pmax所在的点的 p p p改为 0 0 0。(注意,初始的每个点的 p p p并不一样)。
  2. 删除一条边。

思路:
需要注意的是,基本上很多图上删边的题目都是离线倒着处理,但是这道题目特别狗蛋的是前面的询问对后面有后效性。

我们从后往前扫每个删边操作,然后得到每一次删边之前的联通信息,但是如何维护就成了重中之重,同时还要支持查询和修改。

由于每次最多合并两个联通块,所以我们可以开个新点连接这两个联通块,且以后的管理节点在连接这个新的联通块时也是连接在这个点上,这样不难发现这是一个树的结构,而且还是一个二叉树。

在这里插入图片描述
至于快速找到一个点的最高层的管理节点,并查集!!!

而且这个树还有一个很优秀的性质:一个管理节点的子树就是其在分裂前的联通块。

当然,这个树还有许多很好的性质,但是此题没用。

看到第二点,我们很容易想到用 d f s dfs dfs序搞,线段树维护,然后对于每个询问只要在从后往前搞的时候顺便记录一下这个点联通块的最高层管理节点是哪个即可。

#include<cstdio>
#include<cstring>
#define  N  210000
#define  NN  410000
#define  M  310000
using  namespace  std;
inline  int  mymax(int  x,int  y){return  x>y?x:y;}
int  n,m,q;
struct  TREE
{
	int  l,r,c;
}tr[NN];int  len,last[N],p[N],be[N],id[N],valbe[N];
void  bt(int  l,int  r)
{
	int  now=++len;
	if(l==r)tr[now].c=p[be[l]];
	else
	{
		int  mid=(l+r)>>1;
		tr[now].l=len+1;bt(l,mid);
		tr[now].r=len+1;bt(mid+1,r);
		tr[now].c=mymax(tr[tr[now].l].c,tr[tr[now].r].c);
	}
}
void  change(int  now,int  l,int  r,int  id)
{
	if(l==r){tr[now].c=0;return  ;}
	int  mid=(l+r)>>1;
	if(id<=mid)change(tr[now].l,l,mid,id);
	else  change(tr[now].r,mid+1,r,id);
	tr[now].c=mymax(tr[tr[now].l].c,tr[tr[now].r].c);
}
int  findans(int  now,int  l,int  r,int  ll,int  rr)
{
	if(l==ll  &&  r==rr)return  tr[now].c;
	int  mid=(l+r)>>1,lc=tr[now].l,rc=tr[now].r;
	if(rr<=mid)return  findans(lc,l,mid,ll,rr);
	else  if(mid<ll)return  findans(rc,mid+1,r,ll,rr);
	else  return  mymax(findans(lc,l,mid,ll,mid),findans(rc,mid+1,r,mid+1,rr));
}
 
int  fa[NN];
int  findfa(int  x)
{
	if(fa[x]!=x)fa[x]=findfa(fa[x]);
	return  fa[x];
}
 
struct  edge
{
	int  x,y;bool  c;
}a[M];
 
int  son[NN][2],cnt;
inline  int  mer(int  x,int  y)
{
	int  tx=findfa(x),ty=findfa(y);
	if(tx!=ty)
	{
		cnt++;fa[tx]=fa[ty]=fa[n+cnt]=n+cnt;
		son[n+cnt][0]=tx;son[n+cnt][1]=ty;
	}
}
int  ll[NN],rr[NN],top;
inline  void  dfs(int  x)
{
	ll[x]=top+1;
	if(x<=n)id[x]=++top,be[top]=x;
	else
	{
		dfs(son[x][0]);
		dfs(son[x][1]);
	}
	rr[x]=top;
}
 
struct  Query
{
	int  type,x;
}qu[510000];
 
int  main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int  i=1;i<=n;i++){scanf("%d",&p[i]);fa[i]=i;valbe[p[i]]=i;}
	for(int  i=1;i<=m;i++)scanf("%d%d",&a[i].x,&a[i].y);
	for(int  i=1;i<=q;i++)
	{
		scanf("%d%d",&qu[i].type,&qu[i].x);
		if(qu[i].type==2)a[qu[i].x].c=1;
	}
	for(int  i=1;i<=m;i++)
	{
		if(!a[i].c)mer(a[i].x,a[i].y);
	}
	for(int  i=q;i>=1;i--)
	{
		if(qu[i].type==2)mer(a[qu[i].x].x,a[qu[i].x].y),qu[i].type=-1;
		else  qu[i].type=findfa(qu[i].x);
	}
	for(int  i=n+cnt;i>=1;i--)
	{
		if(findfa(i)==i)dfs(i);
	}
	bt(1,n);
	for(int  i=1;i<=q;i++)
	{
		if(qu[i].type!=-1)
		{
			int  x=qu[i].type;
			int  y=findans(1,1,n,ll[x],rr[x]);
			if(y)change(1,1,n,id[valbe[y]]);
			printf("%d\n",y);
		}
	}
	return  0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值