题解 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;
}