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

 

posted @ 2018-12-26 20:04  heyuhhh  阅读(150)  评论(0编辑  收藏  举报