1. 程式人生 > 實用技巧 >Educational Codeforces Round 97 div2

Educational Codeforces Round 97 div2

前言

比賽列表

我的天啊,上次剛說有個簡單的,現在這場比賽就打到我心態爆炸QAQ。

上次還能勉強多一題,這次ZWQ和CLB直接把我摁在地上錘了QAQ,QAQ果然我只能做簡單題嗎QAQ。

A

題意:相當於給你一個區間\([l,r]\),要求選擇一個數字\(a\)使得\(∀x∈[l,r],x\mod 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} \right \rfloor*a\)(不是嚴格下限)

\(a≥\left \lfloor \frac{r+2}{2} \right \rfloor\)\(l≥a≥\left \lfloor \frac{r+2}{2} \right \rfloor≥\frac{r+1}{2}\),當\(a<\left \lfloor \frac{r+2}{2} \right \rfloor\),因為\(l\)的下限要小於\(l≥\frac{r+1}{2}\),所以\([\left \lceil \frac{r+1}{2} \right \rceil,r]\)

區間內不存在任何被\(a\)整除的數字,所以\(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≥\left \lfloor \frac{r+1}{2} \right \rfloor\)

因此只考慮\(a=\left \lfloor \frac{r+1}{2} \right \rfloor\)的情況,不難發現,當\(r\)為奇數時,\(\left \lfloor \frac{r+1}{2} \right \rfloor=\left \lfloor \frac{r+2}{2} \right \rfloor\)

,不成立,因此\(r\)為偶數,此時\(a=\frac{r}{2}\)\(r\mod 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\n");
		else  printf("NO\n");
	}
	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\n",ans);
	}
	return  0;
}

C

給你一個數組\(a\),滿足\(1≤a[i]≤n\),然後你需要構造一個正整數陣列\(b\),其中每個數字都不相同,然後使得\(\sum\limits_{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\n",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\n",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\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;
}

後面兩道暫時不會