[省選集訓2022] 模擬賽16
阿新 • • 發佈:2022-03-24
小Z與函式
題目描述
在 \(2022/3/24\) 上午,\(\tt zxy\) 看到了一個函式:
int get(int n)
{
int res=0;
for(int i=1;i<=n;i++)
{
int vs=0;
for(int j=i;j<=n;j++) if(a[i]<a[j])
swap(a[i],a[j]),res++,vs=1;
res+=vs;
}
return res;
}
有一個長度為 \(n\) 的序列 \(a\),對於 \(a\) 的每個字首,將其作為一個序列 \(a\) 所求得的函式值是多少?
\(T\leq 5,1\leq n\leq 2\cdot 10^5,1\leq a_i\leq n\)
解法
社論:我都會做的題一定是垃圾題。我們可以把總次數分成 \(\tt swap\) 和 \(\tt vs\) 兩部分分別解決。
首先考慮 \(\tt swap\) 的次數,其實就是每個點前面比它小的值的個數之和(相同的值要去重),不難證明。
然後發現這個 \(\tt vs\) 很難做,好像單次計算都要 \(O(n^2)\),那麼我們不妨先考慮如何動態插入,達成總時間複雜度 \(O(n^2)\) 的目標。考慮新加入的數對前面的影響,因為選擇排序原來是選出一個極長上升子序列,然後做這樣的變化:
上圖分別展示了,正常選擇排序時的變化,和我們在序列末尾插入(用紅點表示)對前面的影響。從插入的角度看,我們是選出一個極長下降子序列(首項為第一個 \(<\)
如果你理解了上面的過程,就不難寫出 \(O(n^2)\) 的優秀演算法:
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++) if(a[j]<a[i])
{
swap(a[j],a[i]);ans++;
if(!b[j]) ans++;b[j]|=1;
}
printf("%d ",ans);
}
取出一個極長下降子序列是很難得,但是考慮到每個位置只會被覆蓋一次,我們考慮使用勢能法,把未覆蓋的位置作為勢能來讓複雜度正確。有一個關鍵的 \(\tt observation\)
所以我們維護一個關於值的 \(\tt set\),每次暴力掃描,用樹狀陣列維護排名,那麼要麼刪除這個值,要麼覆蓋一個位置。根據勢能法,時間複雜度 \(O(n\log n)\),程式碼實現極為簡潔。
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <set>
using namespace std;
const int M = 200005;
#define ll long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
void write(ll x)
{
if(x>=10) write(x/10);
putchar(x%10+'0');
}
int T,n,a[M],b[M],use[M];
struct fenwick
{
int b[M];
fenwick() {memset(b,0,sizeof b);}
void clear() {memset(b,0,sizeof b);}
void add(int x)
{
for(int i=x;i<=n;i+=i&(-i)) b[i]++;
}
int ask(int x)
{
int r=0;
for(int i=x;i>0;i-=i&(-i)) r+=b[i];
return r;
}
}A,B;
void work()
{
n=read();long long ans=0;
set<int> s;A.clear();B.clear();
for(int i=1;i<=n;i++)
a[i]=read(),b[i]=use[i]=0;
for(int i=1;i<=n;i++)
{
vector<int> d;
for(auto &x:s)
{
if(x>=a[i]) break;
int p=i-B.ask(x);
if(!b[p]) b[p]=1,ans++;
else if(b[p]) d.push_back(x);
}
for(auto &x:d) s.erase(x);
int y=a[i];ans+=A.ask(y-1);
if(!use[y])
s.insert(y),A.add(y),use[y]=1;
B.add(y);
write(ans),putchar(' ');
}
puts("");
}
signed main()
{
freopen("function.in","r",stdin);
freopen("function.out","w",stdout);
T=read();
while(T--) work();
}