Codeforces Round #504:D. Array Restoration
阿新 • • 發佈:2018-12-26
D. Array Restoration
題目連結:https://codeforces.com/contest/1023/problem/D
題意:
給出一個序列,現在要求對一個全為0的序列執行q次操作,每次操作都要選定一段區間然後將區間上面的值變為i(i為操作的次數)。最終使得0序列變為之前給出的序列。
原序列中如果存在0,那麼說明這個值是任意的。
最後要求輸出經過q次操作之後的序列。
題解:
我們首先可以想到不可行的情況:在原序列中若存在一個數a,假設其出現次數大於1,那麼兩端為a的中間區間部分,是沒有值比a小的。
還有一種情況也不可行,就是原序列中沒有0且最大值小於q。
當把這兩種情況排除過後,就是可行的情況了,之後輸出方案就好了。
輸出方案的時候還要解決0的問題,我的解決方案是讓0等於其右邊或左邊的數,我是將一段連續的0縮點後進行處理的。
程式碼如下:
#include <bits/stdc++.h> #define pii pair<int,int> #define INF 99999999 using namespace std; const int N = 2e5+5; int n,q; int a[N],cnt[N]; struct node{ int l,r; }z[N],All[N]; int f[N][20]; vectorView Code<int> vec[N]; int main(){ cin>>n>>q; int num = 0,mx=0; a[0]=-1;a[n+1]=-1; for(int i=1;i<=n;i++){ scanf("%d",&a[i]); mx=max(mx,a[i]); if(a[i]==0&&a[i-1]!=0){ num++; } if(a[i]==0){ cnt[i]=num; if(a[i-1]!=0) z[num].l=i-1; }else if(a[i-1]==0) z[num].r=i; vec[a[i]].push_back(i); } if(a[n]==0) z[num].r=n+1; int flag = 0; for(int i=1;i<=n;i++){ if(a[i]==0 || vec[a[i]].size()<=1) continue ; int len = vec[a[i]].size()-1; All[a[i]].l=vec[a[i]][0]; All[a[i]].r=vec[a[i]][len]; } for(int i=1;i<=n;i++) for(int j=1;j<=18;j++) f[i][j]=INF; for(int i=1;i<=n;i++) f[i][0]=(a[i]==0 ? INF : a[i]); for(int i=1;i<=n;i++) for(int j=1;j<=16;j++) f[i][j]=min(f[i][j-1],f[i+(1ll<<(j-1))][j-1]); for(int i=1;i<=n;i++){ if(vec[a[i]].size()<=1) continue ; int l = All[a[i]].l,r = All[a[i]].r; l++;r--; int K = log((r-l+1)); int mn = min(f[l][K],f[r-(1<<K)+1][K]); if(mn<a[i]){ flag=1; break ; } } int ok=0; if(q>mx) ok=1; if(ok) for(int i=1;i<=n;i++){ if(a[i]==0){ a[i]=q; ok=2; break ; } } if(flag||ok==1) cout<<"NO"; else{ cout<<"YES"<<endl; for(int i=1;i<=n;i++){ if(a[i]!=0){ cout<<a[i]<<" "; } else{ int l = z[cnt[i]].l,r = z[cnt[i]].r; if(l<1 && r>n) cout<<q<<" "; else if(l<1) cout<<a[r]<<" "; else cout<<a[l]<<" "; } } } return 0; }
之後看了下標程,用的是並查集orz...
標程的想法就比較巧妙了,首先把之前說的第二種不可行情況判斷一下,然後開始騷操作。
其基本思想為:值從大到小進行區間修改的操作,對於當前位置的數而言,用並查集來維護i之後小於或等於它的第一個位置。
因為我們是從大到小開始塗色,對於第一次塗色,肯定能找到第一個小於或等於它的位置;對於第i次塗色,假設當前位置為pos,如果pos+1的值比pos的值大,那麼它之前已經操作過了,說明已經用並查集處理過了,那麼f(pos+1)就是比pos+1的值小的第一個位置;如果此時找到的值還比pos的值大,那麼就繼續尋找;如果比pos的值小,那麼就不可行。
注意上面查詢值時要滿足值的存在區間範圍,並且為了輸出方案,我們還要對區間進行修改,這時只用對0進行修改就好了~比它大的都不用管。
具體程式碼如下:
#include <bits/stdc++.h> using namespace std; const int N =2e5+10; int n,q; int f[N],l[N],r[N]; int a[N]; int find(int x){ return f[x]==x ? f[x] : f[x]=find(f[x]); } int main(){ cin>>n>>q; for(int i=0;i<=q;i++) l[i]=n+1,r[i]=0; for(int i=1;i<=n+1;i++) f[i]=i; for(int i=1;i<=n;i++){ scanf("%d",&a[i]); l[a[i]]=min(l[a[i]],i); r[a[i]]=max(r[a[i]],i); } if(l[q]>r[q]){ if(l[0]>r[0]){ cout<<"NO"; return 0; } a[l[0]]=q; f[l[0]]=l[0]+1; } for(int i=q;i>=0;i--){ for(int j=find(l[i]);j<=r[i];j=find(j)){ if(a[j] && a[j]<i){ cout<<"NO"; return 0; } a[j]=i; f[j]=find(j+1); } } puts("YES"); for(int i=1;i<=n;i++) printf("%d ",a[i]?a[i]:1); return 0; }View Code