zoukankan      html  css  js  c++  java
  • Educational Codeforces Round 97 div2

    前言

    比赛列表

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

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

    A

    题意:相当于给你一个区间([l,r]),要求选择一个数字(a)使得(∀x∈[l,r],xmod a≥frac{a}{2})

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

    在考虑(a≤r),不难发现,(l≥left lfloor frac{r}{a} ight floor*a)(不是严格下限)

    (a≥left lfloor frac{r+2}{2} ight floor)(l≥a≥left lfloor frac{r+2}{2} ight floor≥frac{r+1}{2}),当(a<left lfloor frac{r+2}{2} ight floor),因为(l)的下限要小于(l≥frac{r+1}{2}),所以([left lceil frac{r+1}{2} ight ceil,r])区间内不存在任何被(a)整除的数字,所以(a>r-left lceil frac{r+1}{2} ight ceil≥r-frac{r+1}{2}≥frac{r-1}{2}≥left lfloor frac{r-1}{2} ight floor),所以(a≥left lfloor frac{r+1}{2} ight floor)

    因此只考虑(a=left lfloor frac{r+1}{2} ight floor)的情况,不难发现,当(r)为奇数时,(left lfloor frac{r+1}{2} ight floor=left lfloor frac{r+2}{2} ight floor),不成立,因此(r)为偶数,此时(a=frac{r}{2})(rmod a≡0),不成立。

    时间复杂度:(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
    ");
    		else  printf("NO
    ");
    	}
    	return  0;
    }
    

    B

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

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

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

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

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

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

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

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

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

    证毕。

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

    时间复杂度:(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
    ",ans);
    	}
    	return  0;
    }
    

    C

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

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

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

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

    #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
    ",ans);
    	}
    	return  0;
    }
    

    D

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

    E

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

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

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

    如果(a_{b_{i}}-a_{b_{i-1}}-1<b_{i}-b_{i-1}-1)就输出(-1)

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

    (dp[i]=min(dp[j]+i-j-1)(a[i]-a[j]-1≥j-i-1))

    化简一下条件:(a[i]-j≥a[i]-i)

    然后化简一下转移:(dp[i]-i=min(dp[j]-j-1))

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

    时间复杂度:(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
    ");
    			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
    ",ans);
    	return  0;
    }
    

    F

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

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

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

    先把(a)排序一下。

    (f[i][j])为最大值为(a[i]),填了(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
    ",dp[n][n]);
    	return  0;
    }
    

    G

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

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

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

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

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

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

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

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

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

    而这个树,可以用(AC)机的(last)指针快速建出来。

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

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

    我只会Orz,QAQ。

    口胡没有代码。

  • 相关阅读:
    沉痛悼念乔布斯 (Steven Paul Jobs)
    Linux下文件属性
    window phone开发之动画效果
    简单的UDP收发讯息
    Red Hat Enterprise Linux 5 (红帽子企业版5)下Samba, VSFTP配置与安装
    XNA那些事(六)--WINDOWS PHONE 游戏开发中的3D摄像机
    今天发布iPhone 4s的可能性大一点吧(结果:iPhone4s+CDMA/GSM)
    iPhone开发:如何在iPhone应用中使用自定义字体
    C语言数组与指针详解
    linux 常用命令
  • 原文地址:https://www.cnblogs.com/zhangjianjunab/p/13890448.html
Copyright © 2011-2022 走看看