好久沒有寫過了。。這個也拖了好久才寫。
這場打的很炸裂,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
*/
真心不難,只要能想到開始說的那個交換方法,其實就做出來了。