题解:BZOJ5254 : [Fjwc2018]红绿灯

题目链接
一道毒瘤题

/*
直接贴题解,代码附注释
显然所有询问都要经过至少∑d,只需要考虑除了∑d之外的等待红灯的时间。
将所有询问的时间模g+r,并按时间用set维护。
那么对于每个红灯,在set中可以找出1到2个区间,将里面所有的询问暴力取出,添加一个新点作为等到绿灯后的询问放入。
那么询问与新点之间构成了一棵树结构,每个询问实际的答案为它到根路径上所有点的答案之和。
时间复杂度O(nlogn)
*/ 
#include<bits/stdc++.h> 
#define ll long long 
#define P pair<ll,int>
#define sit set<P>::iterator 
#define N 150010
using namespace std;
int n,f[N],q[N],tot;
ll g,r,m,d[N],x,w[N];
bool v[N];
set<P>T;
ll dfs(int x){
  	if(v[x])return w[x];
  	v[x]=true;
	return w[x]+=dfs(f[x]);
}//树形dp(裸) 
void solve(ll L,ll R,ll K){
  	int cnt=0;
  	for(sit it=T.lower_bound(P(L,0));it!=T.end()&&it->first<=R;it=T.lower_bound(P(L,0))){
    	q[cnt++]=it->second;
    	w[it->second]+=K-it->first;//加上等待红灯的时间 
    	T.erase(it);
 	}//暴力找点连边 
  	if(!cnt)return;//如果没有找到就退出 
  	T.insert(P(K%m,++tot));//插点 
	for(int i=0;i<cnt;i++)f[q[i]]=tot;//记父亲 
}
void calc(ll L){
  	L=(m-L+g)%m;//L变红左时间   
  	ll R=L+r-1,K=R+1;//R变红右时间  K变绿时间
  	solve(L,min(R,m-1),K);//防止实际中L<sum%m,所以取min 
  	if(R>=m)solve(0,R-m,K-m);//算出上述取min的情况还有半段没有计算,K-m是因为其实多算了一段 
}
int main(){
  	//freopen("race.in","r",stdin);
	//freopen("race.out","w",stdout);
	scanf("%d%lld%lld",&n,&g,&r);
  	m=g+r;
  	for(int i=0;i<=n;i++)scanf("%lld",&d[i]);
  	int Q;scanf("%d",&Q);
  	for(int i=1;i<=Q;i++){
	  	scanf("%lld",&w[i]);
		T.insert(P(w[i]%m,i));
  	}
  	ll sum=0;tot=Q;
	for(int i=0;i<=n;i++){
    	sum+=d[i];
    	if(i<n)calc(sum%m);
  	}
  	for(int i=1;i<=Q;i++)
	  	printf("%lld\n",dfs(i)+sum);
  	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_36316033/article/details/83244020