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; }
之后看了下标程,用的是并查集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; }
重要的是自信,一旦有了自信,人就会赢得一切。