ARC149D Simultaneous Sugoroku 题解
这个题考场上没有正解思路,一开始思路不完整就开打FHQ,发现暴力合并就换成维护区间,到最后没有调出来.
先说这个维护区间的做法吧,它可拿步骤分(85).
首先我们需要维护的整个区间在初始时就是左右两个边界(输入的数的第一个和最后一个,因为输入升序).
然后我们每一次判断该区间在我们断点的哪一端,对每个操作将断点向对应方向移动.
用一个op表示当前这个区间在断点左侧还是右侧,然后对它接着分裂,如果哪一步调到中间的一些点处,将这些点记录答案.
(线段树维护答案更新).
这样一步步分裂下去就可以了,复杂度在最差时是\(O((\frac{n}2)×m)\),对暴力来说能多拿几分.
#include<bits/stdc++.h>
typedef long long ll;
#define qr qr()
#define ve vector
#define pa pair<int,int>
#define fi first
#define se second
#define lc t[now].son[0]
#define rc t[now].son[1]
using namespace std;
inline ll qr{
ll x=0;bool f=0;char ch=getchar();
while(ch>57||ch<48)f=(ch=='-')?1:0,ch=getchar();
while(ch>=48&&ch<=57)x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
const int N=2e6+200;
int n,m,tot,cnt,num[N],ask[N];
struct node{
int ans,l,r;
}t[N];
// void pushdown(int rt){
// if(t[rt].ans){
// if(!t[rt<<1].ans)t[rt<<1].ans=t[rt].ans;
// if(!t[rt<<1|1].ans)t[rt<<1|1].ans=t[rt].ans;
// t[rt].ans=0;
// }
// }
void update(int rt,int ans,int l,int r){
if(t[rt].l>=l&&t[rt].r<=r){
t[rt].ans=ans;
return;
}
if(t[rt<<1].r>=l)update(rt<<1,ans,l,r);
if(t[rt<<1].r<r)update(rt<<1|1,ans,l,r);
}
void dfs(int k,int l,int r,int now,int op){
if(k>m){
for(int i=l;i<=r;++i)
num[i]-=now;
return;
}
if(l==r){
// cout<<'>'<<endl;
num[l]-=now;
while(k<=m){
if(num[l]>0)num[l]-=ask[k];
else num[l]+=ask[k];
if(!num[l]){
update(1,k,l,l);
return ;
}
++k;
}return;
}
int pos;
if(op)pos=ask[k]+now;
else pos=now-ask[k];
int st=lower_bound(num+l,num+r+1,pos)-num;
int ed=upper_bound(num+l,num+r+1,pos)-num;
// cout<<k<<' '<<l<<' '<<r<<' '<<st<<' '<<ed<<' '<<now<<' '<<op<<' '<<pos<<endl;
if(st>r)dfs(k+1,l,r,pos,0);
else if(st==ed&&num[st]!=pos){
if(st==r){
// if(num[st]<pos)dfs(k+1,l,st,pos,0);
dfs(k+1,l,st-1,pos,0),dfs(k+1,st,r,pos,1);
}else if(st==l)dfs(k+1,st,r,pos,1);
else{
// cout<<'?'<<endl;
dfs(k+1,l,st-1,pos,0);
dfs(k+1,st,r,pos,1);
}
}else if(st==ed&&num[st]==pos){
update(1,k,st,st);
dfs(k+1,l,st-1,pos,0);
}else if(ed<=r){
update(1,k,st,ed-1);
if(l<=st-1)dfs(k+1,l,st-1,pos,0);
dfs(k+1,ed,r,pos,1);
}else {
update(1,k,st,ed-1);
if(l<=st-1)dfs(k+1,l,st-1,pos,0);
}
}
void build(int rt,int l,int r){
t[rt].l=l;t[rt].r=r;
if(l==r)return;
int md=(l+r)>>1;
build(rt<<1,l,md);
build(rt<<1|1,md+1,r);
}
int get(int rt,int pos){
if(t[rt].ans)return t[rt].ans;
if(t[rt].l==t[rt].r)return t[rt].ans;
// pushdown(rt);
if(t[rt<<1].r>=pos)return get(rt<<1,pos);
else return get(rt<<1|1,pos);
}
void init(){
n=qr;m=qr;
for(int i=1;i<=n;++i)num[i]=qr;
for(int i=1;i<=m;++i)ask[i]=qr;
build(1,1,n);
dfs(1,1,n,0,1);
// cout<<"?????"<<endl;
for(int i=1;i<=n;++i){
int tmp=get(1,i);
if(tmp)printf("Yes %d\n",tmp);
else printf("No %d\n",num[i]);
}
}
int main(){
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
init();
return 0;
}
正解如下:
其实正解不难理解,因为值域不大,我们直接在值域上统计答案即可.
那么观察我们用断点分割开的两个区间,这两个区间在对称位置上答案统计是一致的.
这样我们在区间被分裂时,就无需将它们分开处理,让大的区间向小的区间建边,用最终大区间的答案更新小区间的.
因为每次建边都对称,所以用当前的位置作个对称就是另一个停留的位置.
同时一段已经到达原点,另一端也一定到达.
这个思路下,整个区间\(1e6\)的大小每个点顶多被一个边指向.
整体所有点只用遍历一次,复杂度\(O(1e6)\)
结合官方图示看看(D是分割点):

#include<bits/stdc++.h>
typedef long long ll;
#define qr qr()
#define ve vector
#define pa pair<int,int>
#define fi first
#define se second
using namespace std;
inline ll qr{
ll x=0;bool f=0;char ch=getchar();
while(ch>57||ch<48)f=(ch=='-')?1:0,ch=getchar();
while(ch>=48&&ch<=57)x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
const int N=2e6+200;
int n,m,head[N],num[N],tot,vis[N],la[N],to[N];
struct node{
int t,nx;
}edge[N];
void add(int f,int t){
edge[++tot]={t,head[f]};
head[f]=tot;
}
void dfs(int now){
if(to[now])return;
to[now]=1;
for(int i=head[now];i;i=edge[i].nx){
int t=edge[i].t;
if(vis[now])vis[t]=vis[now];
else la[t]-=la[now];
dfs(t);
}
}
void init(){
n=qr;m=qr;
int pos=0,l=1,r=(int)1e6,tmp;
for(int i=1;i<=n;++i)
num[i]=qr;
for(int i=1;i<=m;++i){
tmp=qr;
// cout<<l<<' '<<r<<endl;
if(l>pos)pos+=tmp;
else if(r<pos)pos-=tmp;
if(pos>=l&&pos<=r){
if(pos-l>=r-pos){
vis[pos]=i;
for(int k=1;k<=r-pos;++k)
add(pos-k,pos+k);
r=pos-1;
}else{
vis[pos]=i;
for(int k=1;k<=pos-l;++k)
add(pos+k,pos-k);
l=pos+1;
}
}
}for(int i=l;i<=r;++i){
la[i]=i-pos;
dfs(i);
}for(int i=1;i<l;++i)
if(vis[i])dfs(i);
for(int i=(int)1e6;i>r;--i)
if(vis[i])dfs(i);
for(int i=1;i<=n;++i){
if(vis[num[i]])printf("Yes %d\n", vis[num[i]]);
else printf("No %d\n",la[num[i]]);
}
}
int main(){
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
init();
return 0;
}
https://www.cnblogs.com/shining-like-stars

浙公网安备 33010602011771号