1. 程式人生 > 其它 >「BalticOI 2016 Day2」交換

「BalticOI 2016 Day2」交換

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\)
\(i/2\),都分別可以選到對方在當前操作能取到的值。
其實交換,肯定其中一個點先選了(另外)一個的值,然後這個值會被刪去,因此另外的一個點就被迫選到另一個值去了。
正確性得以證明_

複雜度,暫時還感覺是個不好卡的暴力。
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;
}