AtCoder AGC001F Wide Swap (线段树、拓扑排序)
題目鏈接: https://atcoder.jp/contests/agc001/tasks/agc001_f
題解: 先變成排列的逆,要求\(1\)的位置最小,其次\(2\)的位置最小,依次排下去(稱之為逆字典序)。有一些條件,如果兩數\(x,y\)的差小于\(K\), 那么它們的相對位置不可變。
所以如果從必須在前面的往必須在后面的連邊,得到的圖將是一個DAG,現在需要求它的一個拓撲序滿足上面的最優化條件。
先排除幾個錯誤結論: 翻轉后字典序越大,字典序越小,錯誤。逆字典序越大,字典序越大/越小,錯誤。
有這樣一個正確結論: 逆字典序最小的拓撲序即為翻轉后字典序最大的拓撲序。注意必須是在一個圖的拓撲序的集合中,不可拓展到任意排列的集合中,而且是“最大”“最小”,不可拓展為“越大”“越小”。
對于這個結論,網上好多感性理解/證明都是明顯有問題的。我給出一個我自己的證明: (可能有錯,有錯請指出) 考慮歸納,假設往圖里添加一個新的點\(n\), 假設在翻轉后字典序最大的拓撲序里\(n\)的位置為\(k\), 那么\(n\)一定要向位置\((k+1)\)上的數連邊(否則交換它們會使得翻轉后字典序更大),即\(n\)現在所處的位置是合法情況下其所處的最靠后的位置。又因為在沒加\(n\)之前該排列是逆字典序最小的,因此加了\(n\)之后會使得后面最小個數的小于\(n\)的數位置后移\(1\), 因此加了之后依然是最小。
所以我們只需要建出圖來求翻轉后字典序最小的拓撲序,然而邊數是\(O(n^2)\)的,但是發現連邊時只需要考慮區間內最小的和最大的即可。線段樹優化。
時間復雜度\(O(n\log n)\)
代碼
#include<cstdio> #include<cstdlib> #include<cstring> #include<utility> #include<algorithm> #include<queue> #define llong long long using namespace std;const int N = 5e5; struct SegmentTree {struct SgTNode{int val;} sgt[(N<<2)+2];void pushup(int pos) {sgt[pos].val = max(sgt[pos<<1].val,sgt[pos<<1|1].val);}void modify(int pos,int le,int ri,int lrb,int val){if(le==lrb && ri==lrb) {sgt[pos].val = val; return;}int mid = (le+ri)>>1;if(lrb<=mid) {modify(pos<<1,le,mid,lrb,val);}else if(lrb>mid) {modify(pos<<1|1,mid+1,ri,lrb,val);}pushup(pos);}int querymax(int pos,int le,int ri,int lb,int rb){if(lb<=le && rb>=ri) {return sgt[pos].val;}int mid = (le+ri)>>1;int ret = 0;if(rb>mid) {ret = max(ret,querymax(pos<<1|1,mid+1,ri,lb,rb));}if(lb<=mid) {ret = max(ret,querymax(pos<<1,le,mid,lb,rb));}return ret;} } smt; struct Edge {int v,nxt; } e[(N<<1)+2]; int a[N+3]; int b[N+3]; int ind[N+3]; int fe[N+3]; priority_queue<pair<int,int> > pq; pair<int,int> ans[N+3]; int fans[N+3]; int n,m,en;void addedge(int u,int v) { // printf("addedge%d %d\n",u,v);en++; e[en].v = v; ind[v]++;e[en].nxt = fe[u]; fe[u] = en; }int main() {scanf("%d%d",&n,&m);for(int i=1; i<=n; i++) {int x; scanf("%d",&b[i]); a[b[i]] = i;}for(int i=1; i<=n; i++){ans[i].second = a[i];int x = smt.querymax(1,0,n,max(a[i]-m+1,0),a[i]);int y = smt.querymax(1,0,n,a[i],min(a[i]+m-1,n));if(x) {addedge(i,x);}if(y) {addedge(i,y);} // printf("iquery %d %d %d\n",i,max(0,a[i]-m+1),min(n,a[i]+m-1)); // printf("imodify %d %d %d\n",i,ans[i].second,ans[i].first);smt.modify(1,0,n,ans[i].second,i); // printf("ans%d %d %d\n",i,ans[i].first,ans[i].second);}for(int i=1; i<=n; i++) if(ind[i]==0) {pq.push(make_pair(a[i],i));}int j = 0;while(!pq.empty()){pair<int,int> tmp = pq.top(); pq.pop();j++; b[j] = tmp.first; int u = tmp.second;for(int i=fe[u]; i; i=e[i].nxt){ind[e[i].v]--;if(ind[e[i].v]==0){pq.push(make_pair(a[e[i].v],e[i].v));}}}for(int i=1; i<n+1-i; i++) swap(b[i],b[n+1-i]);for(int i=1; i<=n; i++) fans[b[i]] = i;for(int i=1; i<=n; i++) printf("%d\n",fans[i]);return 0; }總結
以上是生活随笔為你收集整理的AtCoder AGC001F Wide Swap (线段树、拓扑排序)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AtCoder AGC001E BBQ
- 下一篇: BZOJ 2716 [Violet 3]