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是分割点):
image

#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;
}
posted @ 2024-07-24 08:05  SLS-wwppcc  阅读(17)  评论(0)    收藏  举报