题解:P3279 [SCOI2013] 密码
第一次场紫。
做法为贪心加上线段树维护。
Solution
首先,对于一个中心,会产生两种限制。一种为范围内的字符两两相等,另一种为范围外的两个位置字符不同。
由于要求字典序最小,考虑贪心。
从左到右依次考虑每一个位置的字符要填什么。能填字典序小的肯定填。填完以后遍历被这个位置限制的其他位置,打上标记。
这一段的代码先给出,可以稍微理解一下。
for(int j = 0;j<25;j++)
if(!pd[i][j]){
cout<<(ans[i]=(char)('a'+j));
for(auto t:edge[i])
pd[t][j]=1;
break;
}
但是还有相同的要求。就是对于当前的位置
这里区间推平、单点查询使用的数据结构是线段树。
AC code
#include <bits/stdc++.h>
using namespace std;
#define mid (l+r>>1)
int n,x,tree[400005];
bool pd[100005][26];
char ans[100005];
vector<int> edge[100005];
inline int lc(int x){return x<<1;}
inline int rc(int x){return x<<1|1;}
inline void f(int now,int k){if(!k) return;tree[now]=k;}
inline void update(int now,int l,int r,int nl,int nr,int k){
if(l>nr||r<nl||l>r) return;
if(l>=nl&&r<=nr){
f(now,k);
return;
}
update(lc(now),l,mid,nl,nr,k);
update(rc(now),mid+1,r,nl,nr,k);
}
inline int query(int now,int l,int r,int x){
if(r<x||l>x) return 0;
if(tree[now]) return tree[now];
if(l==r) return 0;
return max(query(lc(now),l,mid,x),query(rc(now),mid+1,r,x));
}
int main(){
cin>>n;
for(int i = 1;i<=n;i++) cin>>x,edge[i-x/2-1].push_back(i+x/2+1),update(1,1,n,i+1,i+x/2,i);
for(int i = 1;i<n;i++) cin>>x,edge[i-x/2].push_back(i+x/2+1),update(1,1,n,i+1,i+x/2,n+i);
for(int i = 1;i<=n;i++){
int tmp=query(1,1,n,i);
if(tmp){
int t;
if(tmp>n)
t=(tmp-n)-(i-tmp+n-1);
else t=tmp-(i-tmp);
cout<<(ans[i]=ans[t]);
for(auto t:edge[i])
pd[t][ans[i]-'a']=1;
}
else
for(int j = 0;j<25;j++)
if(!pd[i][j]){
cout<<(ans[i]=(char)('a'+j));
for(auto t:edge[i])
pd[t][j]=1;
break;
}
}
return 0;
}