Codeforces Round 945 (Div. 2)

HL_ZZP發表於2024-06-03

好久沒有寫過了。。這個也拖了好久才寫。

這場打的很炸裂,rank3600了,當時心態就有點炸裂,然後就擺了好久。連帶著這篇部落格也沒寫。

A題是速通的。
B題看錯題了。。結果一直不會寫,最後還wa了兩發。就很無語。
把任意看成了存在。然後這題就地獄難度了。其實就是直接從高位往低位計算看看每一行的最小的k是多少就可以了。最後對每一位的k取個max。沒難度的。

哦,這場最炸裂的是我沒寫出來C。

還好我當時的思路我現在還記得。當時的思路就是對給的p陣列,先透過調整q讓所有的\(p_i+q_i\)相等,然後再透過調整\(q_i\)來達成答案。
然後就一直出不來。

當時也嘗試過去想想其他的思路,但是都失敗了。
想到了這個思路,然後我就好像沒法像其他的思路了一樣。就很難受。所以到最後我都沒做出來。
其實透過觀察樣例,是可以猜一個結論的,就是不管他怎麼給\(p_i\),答案永遠都是\(\frac n 2-1\),也就是最優秀的答案總是可以構造出來的。

事實上是可以的。

其實可以發現,假如我像欽定一個數字作為低估,讓它小於他兩邊的數字,我總是可以做到的。

假設是1 2 4 3 這裡面我需要讓1作為低估,而4也同樣需要作為低谷。而答案就是2 4 1 3
透過這個其實就可以想到,構造的方法就是把\(q_i\)排列劃分為兩部分,小的部分我們去匹配那些我們需要讓它成為低谷的地方,而大的就是去找那些我們需要它成為高峰的地方。

這樣其實很明顯是可以做到的。只需要調整峰值的分佈位置讓\(p_i=n\)的部分不要在一個我們所欽定為谷底的位置即可。
這種分配方式,假設我要使得最小的1成為比兩邊大的峰頂,只需要把讓這個位置對應的\(q_i=n\),那麼\(a_i=1+n\)而兩邊的位置,最差也就是\(n-1\)\(n-2\)(因為我們可以透過調整我們所欽定的谷底的位置讓\(p_i=n\)成為峰頂),則會被分配1和2,這樣對應的,中間的\(a_i\)就是\(n+1\),而兩邊的\(a_{i-1},a_{i+1}\)就是\(n\),可以發現成立了。而其他部分也是逸一樣的構造方法,可以發現,總是能夠讓那些高峰的位置\(\geq n+1\),低谷的位置\(\leq n\)。這樣也就構造完全了。

透過這種方法,就可以構造出"谷 峰 谷 峰 谷 峰 谷 峰 ....谷 峰 谷 峰 "這樣的\(a_i\),或者是"峰 谷 峰 谷 峰 谷 峰 谷...峰 谷 峰 谷 峰 谷",這兩種不同完全取決於\(p_i=n\)的位置在哪裡。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
	int a=0,b=1;char c=getchar();
	for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
	for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
	return a*b;
}
int a[100001],b[100001];
int main()
{
	int T=read();
	while(T--)
	{
		int n=read(),Maxid=0;
		for(int i=1;i<=n;i++)
		{
			a[i]=read();
			if(a[i]==n)Maxid=i;
		}
		if(Maxid%2)
		{
			priority_queue<pair<int,int> > q1,q2;
			for(int i=1;i<=n;i++)
			{
				if(i%2)//´ó 
				{
					q1.push({a[i],i});
				}
				else
				{
					q2.push({a[i],i});
				}
			}
			for(int i=n/2+1;i<=n;i++)
			{
				b[q1.top().second]=i;
				q1.pop();
			}
			for(int i=1;i<=n/2;i++)
			{
				b[q2.top().second]=i;
				q2.pop();
			}
		}
		else
		{
			priority_queue<pair<int,int> > q1,q2;
			for(int i=1;i<=n;i++)
			{
				if(i%2)
				{
					q2.push({a[i],i});
				}
				else//´ó 
				{
					q1.push({a[i],i});
				}
			}
			for(int i=n/2+1;i<=n;i++)
			{
				b[q1.top().second]=i;
				q1.pop();
			}
			for(int i=1;i<=n/2;i++)
			{
				b[q2.top().second]=i;
				q2.pop();
			}
		}
		for(int i=1;i<=n;i++)
		{
			cout<<b[i]<<' ';
		}
		cout<<endl;
	}
	return 0;
}

這題想要做出來,就是這個構造,分一半的思路。
我真心覺得很難想,沒有思路,主要就是手玩的時候能不能玩出來。這個思路說起來是真的簡單,就是讓小的匹配大的,大的匹配小的,然後最終做到最優。
只能說,積累到了吧。

D

這種複雜的題面,一般都是有一個簡單的做法的。至少是簡潔的做法。
這題就是。這個詢問很明顯是不尋常的。它這個操作所能給到的資訊極其有限,那也就是說,做法可能是很暴力的。

如果不暴力,大機率就是這個操作所給到的資訊相互之間是有些聯絡的,能夠透過推導得到更加有效的資訊。

那就先看看這個思路吧。分析一下它的操作所能夠給到的資訊。

對於\(f(l,r)=x\),自己給\(l,x\),然後它告訴你是否有滿足的\(r\),以及相應的最近的位置。
這個詢問,很明顯只能翻譯這個\(l,r\)區間的有關於\(x\)的資訊,這就是最離譜的地方,在這一的操作中,很明顯,只要給定了\(x\),我是不能透過這個區間滿足\(x\)的條件來推匯出其他除了最大值之外的某個其他的特定的\(x\)的資訊,也就是得不到其他\(x\)在這個區間的適應性的。

那就是在說,這個透過分析詢問得到的資訊之間的聯絡,唯一的能夠做到的就是得到整個陣列的最大值。
\(2n\)的詢問次數不支援我們詢問得到每一個數字,事實上時間複雜度也不允許。

但是我們可以發現,整個陣列的最大值是對我們解題很有幫助的。幫助我們直接確定了\(x\)的範圍。

x只能是最大值的倍數,而且最大隻能達到\(n\times Max\)
而題面中還有一個十分重要的限制,也就是一定要分為\(k\)段。這也就意味著,即使我在最優秀的情況下,我把這個陣列給均分了,最後的\(x\)最多也就是是\(\frac n k \times Max\) 。那我們考慮暴力做。從\(\frac n k 到 1\) ,嘗試\(Max\)的這些倍數作為\(x\)。注意到在最差的情況下,我們的詢問次數是$k\times \frac n k $ 而這個數字是\(\leq n\)的。總共\(2n\)次詢問,先使用\(n\)次來得到最大值,再使用\(n\)次來得到答案。符合要求。

其實看到這題的時候,能夠感覺到一些思路。題面的限制太多,這導致做法要麼就是十分複雜,而且不是巧妙的那種複雜,而是各種東西強行巢狀的複雜,要麼就是大力出奇跡,直接暴力。

這題的限制,主要就是

1.陣列需要分為\(k\)段。這個限制其實是很明顯和下面特殊的詢問操作有一定的聯絡的。很明顯下面詢問得到的結果可以直接當作下一次詢問的內容,然後這樣驗證一個x是否符合答案。

2.特殊的詢問。

這兩個玩意,很特別,也就意味著題目的做法就來著他們。
做出來這題,第一步就是發現\(x\)的範圍限制,不僅要發現是\(Max\)的倍數,還要發現是\(1\)\(\frac n k\)倍之間。
發現這個之後其實就能夠看出來直接暴力做了。
透過\(n\)次得到最大值應該是很基礎的了。這個一眼能看出來。

不過這題我沒做出來其實就是。。這個題面太少見了,我第一次看到這種限制組合再一起得到的題面,而且還是互動題。
也就互動題能這麼幹了吧。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
	int a=0,b=1;char c=getchar();
	for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
	for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
	return a*b;
}
int main()
{
	int T=read();
	while(T--)
	{
		int n=read(),k=read();
		int Max=1;
		for(int i=2;i<=n;i++)
		{
			cout<<"? "<<1<<' '<<n*i<<endl;
			int x=read();
			if(x==n)
			{
				Max=i;
			}
		}
		int ans=-1;
		for(int i=n/k;i>=1;i--)
		{
			int now=1,c=0;
			while(now<=n&&c<k)
			{
				cout<<"? "<<now<<' '<<i*Max<<endl;
				int x=read();
				now=x+1;
				c++;
			}
			if(c==k&&now==n+1)ans=max(ans,i*Max);
		}
		cout<<"! "<<ans<<endl;
		int x=read();
	}
	return 0;
}

E

其實很簡單的題目。

我題解都沒看就寫完了。寫的還飛快。。
只能說賽時後悔沒開這題了。難繃

其實很簡單。考慮這樣的一個排列。1 3 4 2

假如我們要交換3 4的位置,其實很簡單,先交換1 3 ,然後 1 4 ,再1 3 。
我們就成功發現了,這樣是可以交換任意兩個數字的,而我們需要的代價只是\(max(a,b)+1\)
只要這個數字再這個範圍內,就可以做到。而這就是\(r\)的上界。

那麼只需要根據這個性質,其實就能夠統計出所有的答案了。我們計算一下需要移動的,也就是錯位的數字最大的是誰,最小的是誰。
然後根據這個計算答案。

其中有一個細節,就是其實不一定\(l,r\)是不相等的,舉例子就是3 2 1 4,此時,\(l=r=2\)可以滿足。這是上面統計不到的。需要加上答案。
然後就出來了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
	int a=0,b=1;char c=getchar();
	for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
	for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
	return a*b;
}
int a[100001],b[100001];
int main()
{
	int T=read();
	for(int t=1;t<=T;t++)
	{
		int n=read();
		int Max=0,Min=n+1;
		for(int i=1;i<=n;i++)b[i]=0;
		for(int i=1;i<=n;i++)
		{
			a[i]=read();
			if(a[i]!=i)Max=max(Max,a[i]),Min=min(Min,a[i]),b[a[i]]=i;
		}
		int can=0;
		for(int i=1;i<=n;i++)
		{
			if(b[i]==a[i])
			{
				if(can==0)can=i+(a[i]);
				else if(can!=i+a[i])
				{
					can=-1;
					break;
				}
			}
			else
			if(b[i]!=0)
			{
				can=-1;
				break;
			}
		}
		ll ans=0;
		if(Max==0&&Min==n+1)
		{
			cout<<1LL*(1+2*n)*n<<endl;
			continue;
		}
		for(int i=1;i<=Min+n;i++)
		{
			ans+=2*n-max(i,Max);
//			cout<<i<<' '<<2*n-max(i,Max)<<endl;
		}
//		cout<<can<<endl;
		if(can!=0&&can!=-1)ans++;
		cout<<ans<<endl;
	}
	return 0;
}
/*
1
4
3 2 1 4

1
5
2 1 4 5 3 
30

*/

真心不難,只要能想到開始說的那個交換方法,其實就做出來了。

相關文章