Educational Codeforces Round 97 div2

前言

比赛列表

我的天啊,上次刚说有个简单的,现在这场比赛就打到我心态爆炸QAQ。

上次还能勉强五五开,这次ZWQ和CLB直接把我摁在地上锤了QAQ,QAQ果然我只能做简单题吗QAQ。

A

题意:相当于给你一个区间 [ l , r ] [l,r] [l,r],要求选择一个数字 a a a使得 ∀ x ∈ [ l , r ] , x m o d    a ≥ a 2 ∀x∈[l,r],x\mod a≥\frac{a}{2} x[l,r],xmoda2a

题解:看样例,提示的十分明显, [ 3 , 4 ] [3,4] [3,4] 5 5 5,不妨考虑 a = r + 1 a=r+1 a=r+1,这样的话区间只要 l ≥ r + 1 2 l≥\frac{r+1}{2} l2r+1即可,但是呢,为什么不存在比这个更加优秀的呢?不难发现,如果 a > r a>r a>r,那么 a a a越大, l l l的下限也越大, a = r + 1 a=r+1 a=r+1是最优秀的。

在考虑 a ≤ r a≤r ar,不难发现, l ≥ ⌊ r a ⌋ ∗ a l≥\left \lfloor \frac{r}{a} \right \rfloor*a lara(不是严格下限)

a ≥ ⌊ r + 2 2 ⌋ a≥\left \lfloor \frac{r+2}{2} \right \rfloor a2r+2 l ≥ a ≥ ⌊ r + 2 2 ⌋ ≥ r + 1 2 l≥a≥\left \lfloor \frac{r+2}{2} \right \rfloor≥\frac{r+1}{2} la2r+22r+1,当 a < ⌊ r + 2 2 ⌋ a<\left \lfloor \frac{r+2}{2} \right \rfloor a<2r+2,因为 l l l的下限要小于 l ≥ r + 1 2 l≥\frac{r+1}{2} l2r+1,所以 [ ⌈ r + 1 2 ⌉ , r ] [\left \lceil \frac{r+1}{2} \right \rceil,r] [2r+1,r]区间内不存在任何被 a a a整除的数字,所以 a > r − ⌈ r + 1 2 ⌉ ≥ r − r + 1 2 ≥ r − 1 2 ≥ ⌊ r − 1 2 ⌋ a>r-\left \lceil \frac{r+1}{2} \right \rceil≥r-\frac{r+1}{2}≥\frac{r-1}{2}≥\left \lfloor \frac{r-1}{2} \right \rfloor a>r2r+1r2r+12r12r1,所以 a ≥ ⌊ r + 1 2 ⌋ a≥\left \lfloor \frac{r+1}{2} \right \rfloor a2r+1

因此只考虑 a = ⌊ r + 1 2 ⌋ a=\left \lfloor \frac{r+1}{2} \right \rfloor a=2r+1的情况,不难发现,当 r r r为奇数时, ⌊ r + 1 2 ⌋ = ⌊ r + 2 2 ⌋ \left \lfloor \frac{r+1}{2} \right \rfloor=\left \lfloor \frac{r+2}{2} \right \rfloor 2r+1=2r+2,不成立,因此 r r r为偶数,此时 a = r 2 a=\frac{r}{2} a=2r r m o d    a ≡ 0 r\mod a≡0 rmoda0,不成立。

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

#include<cstdio>
#include<cstring>
using  namespace  std;
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)
	{
		int  l,r;scanf("%d%d",&l,&r);
		int  mid=r+1;
		if(l>=(mid/2+(mid&1)))printf("YES\n");
		else  printf("NO\n");
	}
	return  0;
}

B

题意:给你一串 01 01 01字符串,长度为 n n n n n n 偶 数 偶数 ,其中一半为 0 0 0,一半为 1 1 1,然后每次操作可以选择 [ l , r ] [l,r] [l,r]翻转,问最少几次操作可以变成 s i ≠ s i + 1 s_i≠s_{i+1} si=si+1的01串,即: 01010101... 01010101... 01010101...或者 10101010101... 10101010101... 10101010101...

题解:只有两个标准字符串,不妨考虑先转化成 01010101... 01010101... 01010101...,我们称这个为标准字符串,记为 B B B,原字符串为 A A A,建立新的字符串: C C C C i = [ B i ≠ A i ] C_{i}=[B_{i}≠A_{i}] Ci=[Bi=Ai]

不难发现,对于 C C C中的字符串,选择 [ l , r ] [l,r] [l,r]进行翻转, r − l + 1 r-l+1 rl+1为偶数时,翻转完之后还要对此区间取反,而奇数时便是单纯的翻转。

把一段连续的 1 1 1成为联通块。

分几种情况讨论不难证明每次翻转最多消除一个联通块,所以答案 ≥ ≥ 联通块数量。

那么现在构造一种方案等于联通块数量。

如果一个联通块的,长度为偶数,直接翻转,这样这会剩下偶数个奇数长度的联通块。

然后对于相邻的两个联通块,如果中间 0 0 0的个数为偶数个,则将一个联通块和中间的 0 0 0翻转,使两个联通块合并成偶数联通块并且一次消除掉。

但是如果相邻的联通块中间都是奇数个 0 0 0呢?其实通过构造 C C C的方法以及 0 , 1 0,1 0,1个数相同,我们不难得到一个结论,奇数位的 1 1 1等于偶数位的 1 1 1,因此不存在相邻联通块中间都是奇数个 1 1 1的情况。

证毕。

然后只要两个标准字符串都搞一遍就可以了。

时间复杂度: 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  a[N],b[N],n;
char  st[N];
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		scanf("%s",st+1);
		for(int  i=1;i<=n;i++)a[i]=st[i]-'0';
		int  ans,sum=0;
		for(int  i=1;i<=n;i++)b[i]=((a[i]&1)==(i&1));
		for(int  i=1;i<=n;i++)
		{
			if(b[i]==1  &&  b[i-1]==0)sum++;
		}
		ans=sum;
		for(int  i=1;i<=n;i++)b[i]=((a[i]&1)!=(i&1));
		sum=0;
		for(int  i=1;i<=n;i++)
		{
			if(b[i]==1  &&  b[i-1]==0)sum++;
		}
		ans=mymin(sum,ans);
		printf("%d\n",ans);
	}
	return  0;
}

C

给你一个数组 a a a,满足 1 ≤ a [ i ] ≤ n 1≤a[i]≤n 1a[i]n,然后你需要构造一个正整数数组 b b b,其中每个数字都不相同,然后使得 ∑ i = 1 n ∣ b [ i ] − a [ i ] ∣ \sum\limits_{i=1}^n|b[i]-a[i]| i=1nb[i]a[i]最小,问这个值最小是多少。

做法:把 a a a排序,不难发现, b n ≤ 2 n b_{n}≤2n bn2n(事实上,同机房大佬说 3 n 2 \frac{3n}{2} 23n就够了,想想也是), b i < b i + 1 b_{i}<b_{i+1} bi<bi+1

然后跑个 n 3 n^3 n3的DP即可,事实上,可以优化到 O ( n 2 ) O(n^2) O(n2)

时间复杂度: O ( n 3 ) O(n^3) O(n3)

#include<cstdio>
#include<cstring>
#include<algorithm>
#define  N  410
using  namespace  std;
int  dp[N][N],n,a[N];
inline  int  zabs(int  x){return  x<0?-x:x;}
inline  int  mymin(int  x,int  y){return  x<y?x:y;}
inline  bool  cmp(int  x,int  y){return  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]);
		sort(a+1,a+n+1,cmp);
		int  limit=2*n;
		for(int  i=1;i<=limit;i++)dp[1][i]=zabs(a[1]-i);
		for(int  i=2;i<=n;i++)
		{
			for(int  j=1;j<=limit;j++)
			{
				dp[i][j]=999999999;
				for(int  k=j-1;k>=1;k--)dp[i][j]=mymin(dp[i-1][k]+zabs(a[i]-j),dp[i][j]);
			}
		}
		int  ans=999999999;
		for(int  i=1;i<=limit;i++)ans=mymin(ans,dp[n][i]);
		printf("%d\n",ans);
	}
	return  0;
}

D

题意:给你一个BFS遍历顺序,要求你按照这个顺序找到满足要求的树中高度最小的,输出最小高度,满足对于一个点,会直接将其儿子全部加入到队列中(也就是在BFS顺序中是连续一段的),且儿子被丢进去的顺序是按照儿子的编号从小到大丢的,根固定为 1 1 1,高度为 0 0 0

做法:我们将顺序中每个递增的连续一小段称为联通块。

不难发现,每个儿子接一个联通块是最优秀的(不难发现,如果只接一般的联通块,一定不会比这种方法优秀),所以下一层的点数一定大于等于这一层的点数。

所以贪心做一下就行了。

#include<cstdio>
#include<cstring>
#define  N  210000
using  namespace  std;
int  n,a[N];
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		for(int  i=1;i<=n;i++)scanf("%d",&a[i]);
		int  now=1,used=0,tot=0,h=0,pre=0;
		for(int  i=2;i<=n;i++)
		{
			if(a[i]>pre)pre=a[i],tot++;
			else
			{
				used++;pre=a[i];
				if(used==now)
				{
					h++;used=0;
					now=tot;tot=1;
				}
				else  tot++;
			}
		}
		if(n>1)h++;
		printf("%d\n",h);
	}
	return  0;
}

E

题意:给你 a , b a,b a,b数组,规定一个操作: x , i x,i x,i a [ x ] = i a[x]=i a[x]=i x x x不在 b b b数组中, b b b数组严格递增且范围在 [ 1 , n ] [1,n] [1,n]中,长度为 k k k a a a数组长度为 n n n

然后问你能不能通过最少的操作数把 a a a数组变成严格递增,不能输出 − 1 -1 1,能输出最小操作数。

做法:额,首先根据 b b b数组分成 k + 1 k+1 k+1块,经CLB提示,为了代码方便,会在数组两端加入哨兵点,顺利的把 a a a数组禁锢在 b b b数组中(就是 b [ 0 ] = 0 , b [ k + 1 ] = n + 1 b[0]=0,b[k+1]=n+1 b[0]=0,b[k+1]=n+1,同时 a a a数组做出类似的变化)。

如果 a b i − a b i − 1 − 1 < b i − b i − 1 − 1 a_{b_{i}}-a_{b_{i-1}}-1<b_{i}-b_{i-1}-1 abiabi11<bibi11就输出 − 1 -1 1

然后考虑对于每一段跑 D P DP DP求出最小的操作数变成递增的, d p [ i ] dp[i] dp[i] i i i不改变时前面变成递增的最小操作数。

d p [ i ] = m i n ( d p [ j ] + i − j − 1 ) ( a [ i ] − a [ j ] − 1 ≥ j − i − 1 ) dp[i]=min(dp[j]+i-j-1)(a[i]-a[j]-1≥j-i-1) dp[i]=min(dp[j]+ij1)(a[i]a[j]1ji1)

化简一下条件: a [ i ] − j ≥ a [ i ] − i a[i]-j≥a[i]-i a[i]ja[i]i

然后化简一下转移: d p [ i ] − i = m i n ( d p [ j ] − j − 1 ) dp[i]-i=min(dp[j]-j-1) dp[i]i=min(dp[j]j1)

然后这个用线段树维护一下就行了,顺便注意一下细节。(当然,同机房大佬也有用树状数组的,还有师兄用类似的方法直接求了最长上升子序列,应该也是同样的原理)

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

#include<cstdio>
#include<cstring>
#include<algorithm>
#define  N  510000
#define  SN  11000000
using  namespace  std;
inline  int  mymin(int  x,int  y){return  x<y?x:y;}
struct  node
{
	int  l,r,c;
}tr[SN];int  len,rt;
inline  int  addnode(){len++;tr[len].l=tr[len].r=tr[len].c=0;return  len;}
inline  void  updata(int  x){tr[x].c=mymin(tr[tr[x].l].c,tr[tr[x].r].c);}
inline  void  link(int  &x,int  l,int  r,int  k,int  c)
{
	if(!x)x=addnode();
	if(l==r){tr[x].c=c;return  ;}
	int  mid=(l+r)>>1;
	if(k<=mid)link(tr[x].l,l,mid,k,c);
	else  link(tr[x].r,mid+1,r,k,c);
	updata(x);
}
inline  int  findans(int  x,int  l,int  r,int  c/*小于等于k的*/)
{
	if(r<=c)return  tr[x].c;
	if(!x)return  0;
	int  mid=(l+r)>>1;
	if(mid>=c)return  findans(tr[x].l,l,mid,c);
	else  return  mymin(findans(tr[x].l,l,mid,c),findans(tr[x].r,mid+1,r,c));
}
int  a[N],b[N];
int  id[N],cnt/*离散化*/,be[N],dp[N];
int  n,k;
inline  bool  cmp(int  x,int  y){return  a[x]<a[y];}
int  main()
{
	scanf("%d%d",&n,&k);
	for(int  i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		a[i]-=i;
		id[i]=i;
	}
	sort(id+1,id+n+1,cmp);
	for(int  i=1;i<=n;i++)
	{
		if(a[id[i]]!=be[cnt])cnt++,be[cnt]=a[id[i]];
		a[id[i]]=cnt;
	}
	for(int  i=1;i<=k;i++)scanf("%d",&b[i]);
	for(int  i=2;i<=k;i++)
	{
		if(a[b[i]]<a[b[i-1]])
		{
			printf("-1\n");
			return  0;
		}
	}
	//b[0]=0,a[0]=0;
	cnt++;b[++k]=n+1;a[n+1]=cnt;//哨兵节点 
	int  ans=0;
	for(int  i=1;i<=k;i++)
	{
		len=rt=0;//清除掉整棵树 
		int  l=b[i-1]+1,r=b[i],limit=a[l-1]/*大于这个数字才有资格成为不减的象征*/;
		link(rt,0,cnt,limit,-b[i-1]-1/*十分的有诱惑性*/);
		for(int  j=l;j<=r;j++)
		{
			if(a[j]>=limit)
			{
				dp[j]=findans(rt,0,cnt,a[j])+j;
				link(rt,0,cnt,a[j],dp[j]-j-1);
			}
		}
		ans+=dp[r];
	}
	printf("%d\n",ans);
	return  0;
}

F

题意:现在有 n n n个数字的数组 a a a,要求你求满足要求的排列 b b b的数量。

要求:对于 b [ i ] b[i] b[i]而言,设 y = m a x ( a [ b [ j ] ] ) ( j < i ) y=max(a[b[j]])(j<i) y=max(a[b[j]])(j<i),那么要求 a [ b [ i ] ] ≥ 2 y a[b[i]]≥2y a[b[i]]2y或者 2 a [ b [ i ] ] ≤ y 2a[b[i]]≤y 2a[b[i]]y

做法:艹,看错题了,不然超级弱智,也就G不会了

先把 a a a排序一下。

f [ i ] [ j ] f[i][j] f[i][j]为最大值为 a [ i ] a[i] a[i],填了 j j j个数字的方案数,然后暴力转移。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define  N  5100
using  namespace  std;
typedef  long  long  LL;
LL  dp[N][N],f[N],mod=998244353;
int  n,a[N],id[N];
int  main()
{
	scanf("%d",&n);
	for(int  i=1;i<=n;i++)scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	int  l=0;
	for(int  i=1;i<=n;i++)
	{
		while(a[l+1]<=a[i]/2)l++;
		id[i]=l;
		f[i]=1;
	}
	f[0]=1;
	for(int  i=1;i<=n;i++)//一层一层往上DP 
	{
		for(int  j=1;j<=n;j++)
		{
			if(id[j]+1<i)dp[i][j]=0;
			else  dp[i][j]=(f[id[j]]+dp[i-1][j]*(id[j]-i+2))%mod;
		}
		f[0]=0;for(int  j=1;j<=n;j++)f[j]=(f[j-1]+dp[i][j])%mod;
	}
	printf("%lld\n",dp[n][n]);
	return  0;
}

G

题意:给你 n n n个字符串,每个字符串有个 a a a值,有两种操作。

  1. 修改第 i i i个字符串的 a a a值。
  2. 给你一个字符串 T T T,问你 m i n ( a i ) ( S i 是 T 的 字 串 ) min(a_{i})(S_{i}是T的字串) min(ai)(SiT)

初始给出的字符串长度总和 3 e 5 3e5 3e5,询问的字符串长度总和 3 e 5 3e5 3e5

做法:感谢同机房大佬提供的思路(我目前只停留在口胡阶段),Orz。

以前一直以为处理子串信息只能用后缀自动机。

事实上,我现在也这么认为的。

但是大佬提供了一种新式思考,因为字符串总和 3 e 5 3e5 3e5,所以不妨处理 T i T_{i} Ti T i T_{i} Ti T T T字符串中 1 1 1~ i i i的字符组成的字符串)的后缀的值。

那什么可以快速处理一个字符串的后缀呢? A C AC AC机啊。

我们不妨考虑建一棵树,树上的点就是开始给出的 n n n个点,一个点的儿子的后缀就是这个点的字符串,如果名字相同,则编号大的为儿子(因此,在 A C AC AC机中,每个点如果对应多个编号,直接用编号大的),然后这个树用树剖维护。

而这个树,可以用 A C AC AC机的 l a s t last last指针快速建出来。

然后对于每个询问,一个字符一个字符跑 A C AC AC机,然后对于每个字符跑完后的位置所对应的 l a s t last last,求一下 l a s t last last在我们新建的树中到根节点的路径上的最小值。

时间复杂度: O ( ( n + q ) l o g 2 n ) O((n+q)log^2n) O((n+q)log2n)。(但是常数非常的小)

我只会Orz,QAQ。

口胡没有代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值