「BalticOI 2016 Day2」交換
阿新 • • 發佈:2022-05-05
description
給一個\(1~n\)的排列\(x_1,x_2,x_3...x_{n-1},x_n\)。
有\(n-1\)輪交換,每輪可以選擇是否交換\(x_i\)和\(x_{i/2}\)
輸出可以得到的最小字典序的最終序列。
solution
我康了luogu的兩篇題解,第一篇思路還有點道理,但是那種繁雜討論的貪心,
因此我挑了第二篇好寫的題解,但是思路比較奇怪,而且還不會證複雜度(或許本身複雜度就是偽的)。但只要出題人想不到這種思路就不會被卡……
肯定核心思路:從小到大確定每個點的值,每次找能取的儘量小的。
面對交換真的不知所措,可以還是從點選值的角度看待交換。
交換\(i,i/2\)
看成選\(i\)時,可以換/不換為\(i/2\)。
及選\(i/2\)時,可以換/不換為\(i\)。
這樣有點二叉樹的感覺(雖然很牽強),考慮如何定義節點與構樹。
首先所有點建一個點,點權為初值。(葉子,意義為最後選到的點,後面如果選到了會被賦為inf即刪掉)
建樹時從前往後列舉每一種交換。
\(lst[i]\)表示\(i\)位置最後新建的節點。
對於上面的交換,兩個操作分別建一個虛點。
兩個虛點都連向\(lst[i/2]\)和\(lst[i]\)連邊,更新\(lst[i/2]\)和\(lst[i]\)。
聰明的你一定能猜出上面連邊的含義了:一個兒子代表不交換,另一個代表交換。
所以這兩個不同的虛點,連邊代表的含義不同。
同時,每個虛點都會有一個標記用於刪除(比較好yy,具體看程式碼就懂了),為1表示要交換(今後兩個兒子中都只會固定一個了),為0表示現在還沒交換(以後不一定)。
實際上,更接近問題本質(點選值)可以證明正確性的理解很簡單:\(i\)
其實交換,肯定其中一個點先選了(另外)一個的值,然後這個值會被刪去,因此另外的一個點就被迫選到另一個值去了。
正確性得以證明_
複雜度,暫時還感覺是個不好卡的暴力。
emmm後面再看看吧。
easy code:
點選檢視程式碼
#include<bits/stdc++.h> using namespace std; const int N=1e6+5; int a[N],tot,lst[N],n; struct node {int ls,rs,val;bool chg;}nd[N]; int _find(int u) { if(nd[u].val)return nd[u].val; if(nd[u].chg) return _find(nd[u].ls); else return min(_find(nd[u].ls),_find(nd[u].rs)); } bool Del(int x,int val) { if(nd[x].val) { if(nd[x].val==val) {nd[x].val=n+1;return 1;} else return 0; } if(nd[x].chg)return Del(nd[x].ls,val); if(Del(nd[x].ls,val)) {nd[x].chg=1;return 1;} else if(Del(nd[x].rs,val)) {nd[x].chg=1;swap(nd[x].ls,nd[x].rs);return 1;} return 0; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); nd[++tot]=(node){0,0,a[i],0}; lst[i]=tot; } for(int i=2;i<=n;i++) { int x=lst[i>>1],y=lst[i]; lst[i>>1]=++tot;nd[tot]=(node){x,y,0,0}; lst[i]=++tot;nd[tot]=(node){x,y,0,0}; } for(int i=1;i<=n;i++) { int x=_find(lst[i]); Del(lst[i],x); printf("%d ",x); } return 0; }