题解 Codeforces 404E The Classic Problem

题意

给定一张 \(n\) 个点 \(m\) 条边的有向图,第 \(i\) 条边的边权为 \(2^{x_i}\),求 \(s\)\(t\) 的最短路。

答案对 \(10^9+7\) 取模。

\(1 \leq n, m \leq 10^5,0 \leq x_i \leq 10^5\)

题解

求最短路?上 Dijkstra。

接下来的问题是需要一个数据结构维护这大的离谱的最短路,支持加上一个 \(2\) 的非负整数次幂和比较大小。

注意到,边权都是 \(2\) 的非负整数次幂,那么我们可以把加法和比较大小在二进制意义下处理。此时加法变为区间赋值 \(0\) 和单点赋值 \(1\) 的操作,可以用主席树维护。

对于比较大小,我们可以记录下每个区间的 hash 值。如果左区间的 hash 值相等则比较右区间,否则比较左区间,直到递归到叶节点为止。

实现略微复杂。

# include <bits/stdc++.h>

typedef long long ll;
const int N=200010,INF=0x3f3f3f3f;
const ll MOD=1e9+7;
struct Edge{
	int to,next,v;
}edge[N<<1];
int head[N],sum;
ll powd[N];


struct Node{
	int lc,rc;
	bool full; // 记录区间是否全 1
	ll w;
}tree[N*40];

int root[N],cnt;
int n,m,xlim;
int s,t;
int pre[N],preval[N];
bool c[N];
bool calc[N];
int sta[N],scnt;
inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-')f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}
inline int& lc(int x){
	return tree[x].lc;
}
inline int& rc(int x){
	return tree[x].rc;
}
inline void pushup(int k){
	tree[k].w=(tree[lc(k)].w+tree[rc(k)].w)%MOD;
	tree[k].full=tree[lc(k)].full&&tree[rc(k)].full;
	return;
}
int liquery(int &k,int l,int r){ // 查询区间内部最长的全 1 前缀
	if(!k)
		return 0;
	int mid=(l+r)>>1;
	return (tree[lc(k)].full)?(mid-l+1+liquery(rc(k),mid+1,r)):(liquery(lc(k),l,mid));
}
std::pair <int,bool> query(int &k,int l,int r,int x){
    // 查询 l r 区间内 从 x 开始最长的连续 1 长度
    // pair.first 是 1 的长度 pair.second 表示区间右端点是否为 1
	if(!k)
		return std::make_pair(0,false);
	if(l==r)
		return std::make_pair((tree[k].w>0),(tree[k].w>0));
	int mid=(l+r)>>1;
	if(x>mid)
		return query(rc(k),mid+1,r,x); // 起点在右侧 那就没啥事了
	std::pair <int,bool> res=query(lc(k),l,mid,x); // 查询左侧的答案
	if(!res.second) // 如果左区间的右端点不为 1 那么说明加上右区间也不可能凑出连续的 1 了
		return res;
	if(tree[rc(k)].full) // 如果右区间全是 1 就不用查询 直接返回区间长度
		return std::make_pair(res.first+r-mid,true);
	return std::make_pair(liquery(rc(k),mid+1,r)+res.first,false); // 查询长度(该操作最多调用 1 次)
}
inline void changezero(int &k,int lst,int l,int r,int L,int R){ // 区间赋值 0
	if(!lst)
		return;
	if(L>R){
		k=lst;
		return;
	}
	if(L<=l&&r<=R){
		k=0;
		return;
	}
	k=++cnt,tree[k]=tree[lst];
	int mid=(l+r)>>1;
	if(L<=mid)
		changezero(lc(k),lc(lst),l,mid,L,R);
	if(mid<R)
		changezero(rc(k),rc(lst),mid+1,r,L,R);
	pushup(k);
	return;
}
void changeone(int &k,int lst,int l,int r,int x){ // 单点赋值 1
	k=++cnt,tree[k]=tree[lst];
	if(l==r){
		tree[k].w=powd[l],tree[k].full=true;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid)
		changeone(lc(k),lc(lst),l,mid,x);
	else
		changeone(rc(k),rc(lst),mid+1,r,x);
	pushup(k);
	return;
}
bool compare(int a,int b,int l,int r){ // is (a>b) true ?
	if(l==r){
		return tree[a].w>tree[b].w;
	}
	int mid=(l+r)>>1;
	if(tree[rc(a)].w==tree[rc(b)].w)
		return compare(lc(a),lc(b),l,mid);
	return compare(rc(a),rc(b),mid+1,r);
}
struct Heapval{
	int id,rt;
	bool operator < (const Heapval &rhs) const{
		return compare(rt,rhs.rt,0,xlim);
	}
};
std::priority_queue <Heapval> q;
inline void add(int x,int y,int v){
	edge[++sum].to=y;
	edge[sum].next=head[x];
	edge[sum].v=v;
	head[x]=sum;
	return;
}
inline void Dijkstra(void){
	q.push((Heapval){s,0});
	calc[s]=true;
    // calc 表示当前的值是否被计算过
    // 如果没有被计算过默认为无穷大 不进行主席树的大小比较
	while(!q.empty()){
		int i=q.top().id,irt=q.top().rt;
		q.pop();
		if(c[i])
			continue;
		c[i]=true;
		for(int j=head[i];j;j=edge[j].next){
			int to=edge[j].to;
			if(c[to])
				continue;
			int sid=edge[j].v+query(irt,0,xlim,edge[j].v).first,temp=0;
			changezero(temp,irt,0,xlim,edge[j].v,sid-1);
			changeone(temp,temp,0,xlim,sid); // 模拟加法
			if(!calc[to]||(calc[to]&&compare(root[to],temp,0,xlim))){
				calc[to]=true;
				pre[to]=i,preval[to]=edge[j].v,root[to]=temp;
				q.push((Heapval){to,root[to]});
			}
		}
	}
	return;
}
int main(void){
	n=read(),m=read();
	for(int i=1;i<=m;++i){
		int u=read(),v=read(),w=read();
		add(u,v,w),add(v,u,w),xlim=std::max(xlim,w);
	}
	s=read(),t=read();
	powd[0]=1;
	for(int i=1;i<=2e5-10;++i){
		powd[i]=powd[i-1]*2ll%MOD;
	}
	xlim+=30; // 最多可能往后进 log 次位
	Dijkstra();
	if(!c[t])
		puts("-1");
	else{
		ll ans=0;
		while(pre[t]){
			ans=(ans+powd[preval[t]])%MOD,sta[++scnt]=t,t=pre[t];
		}
		printf("%lld\n%d\n%d ",ans,scnt+1,s);
		for(int i=scnt;i;--i)
			printf("%d ",sta[i]);
	}
	return 0;
}

原文地址:https://www.cnblogs.com/liuzongxin/p/15256799.html