1. 程式人生 > >Codeforces Round #504:D. Array Restoration

Codeforces Round #504:D. Array Restoration

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];
vector 
<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; }
View Code

之後看了下標程,用的是並查集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