日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

AtCoder AGC001F Wide Swap (线段树、拓扑排序)

發布時間:2025/3/15 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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 (线段树、拓扑排序)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。