CF464E The Classic Problem 题解
Description
Solution
emm……这是我为数不多的黑题之一,所以来写篇博客记录一下。
我们发现边权过大,只能用高精度来算,但是这样的复杂度太劣了,无法通过此题。
观察到边权只能是 \(2^x\),所以我们可以给它压成二进制数,然后跑最短路时单点加。
我们再来考虑一下 \(dijkstra\) 需要哪些操作:
-
区间加
-
比大小
先来看区间加,如果第 \(x\) 位为 0,那么赋值为 1 即可,如果第 \(x\) 位为一,那么我们需要向上找第一个为 0 的位,然后这一段区间赋值为 0,第一个为 0 的位赋值为 1。
这个查找操作可以使用线段树上二分来实现,据说暴力查找也是可以过的,数据比较水。
那区间赋值为 0 如何实现呢?主席树似乎不太能支持修改。我们可以建一棵全 0 主席树,然后修改时直接把这棵全 0 的树拍上去。
再来看区间比大小,直接比较区间和即可。
然后就是代码实现了,个人认为非常有助于提升码力,也让我对主席树的理解更深了一步。
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <cmath>
#define ull unsigned long long
#define ll long long
#define ls(x) t[x].l
#define rs(x) t[x].r
using namespace std;
inline int read(){
int x = 0;
char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x;
}
const ll mod = 1e9 + 7;
const int N = 2e5 + 10;
int n, m, S, T;
struct node{
int v, w, nxt;
}edge[N << 1];
int head[N], tot;
int inf;
ll fac[N];
struct Tree{
ll sum;//sum:区间和
int l, r, num;//num:区间 1 的个数
friend bool operator == (Tree a, Tree b){
return a.sum == b.sum && a.num == b.num;
}
}t[N * 40];
int root[N], cnt;//跟,节点编号
int ans[N], path, pre[N];//记录路径
bool vis[N];
inline void add(int x, int y, int z){
edge[++tot] = (node){y, z, head[x]};
head[x] = tot;
}
inline void pushup(int rt){
t[rt].num = t[ls(rt)].num + t[rs(rt)].num;
t[rt].sum = (t[ls(rt)].sum + t[rs(rt)].sum) % mod;
}
inline void build(int &rt, int l, int r, int val){//建树
if(!rt) rt = ++cnt;//一定要判断,不然会 RE
if(l == r){
t[rt].sum = fac[l] * val, t[rt].num = val;
return;
}
int mid = (l + r) >> 1;
build(ls(rt), l, mid, val);
build(rs(rt), mid + 1, r, val);
pushup(rt);
}
inline int query(int rt, int L, int R, int l, int r){//查询区间 1 的个数
if(L <= l && r <= R) return t[rt].num;
int mid = (l + r) >> 1;
int res = 0;
if(L <= mid) res += query(ls(rt), L, R, l, mid);
if(R > mid) res += query(rs(rt), L, R, mid + 1, r);
return res;
}
inline void update(int &rt, int l, int r, int val){//加点, 0 改为 1
t[++cnt] = t[rt];
rt = cnt;
if(l == r){
t[rt].sum = fac[l], t[rt].num = 1;
return;
}
int mid = (l + r) >> 1;
if(val <= mid) update(ls(rt), l, mid, val);
else update(rs(rt), mid + 1, r, val);
pushup(rt);
}
inline void clear(int &x, int y, int L, int R, int l, int r){//区间赋 0,把 y(全 0 树) 全部贴到 x 上
if(L <= l && r <= R){
x = y;
return;
}
int mid = (l + r) >> 1, z = ++cnt;
t[z] = t[x];
if(L <= mid) clear(ls(z), ls(y), L, R, l, mid);
if(R > mid) clear(rs(z), rs(y), L, R, mid + 1, r);
pushup(x = z);
}
inline int search(int rt, int l, int r, int val){//线段树上二分,向上找第一个不为 1 的位置
if(l == r) return l;
int mid = (l + r) >> 1;
if(val > mid) return search(rs(rt), mid + 1, r, val);//查找权值在右子树
if(query(ls(rt), val, mid, l, mid) == mid - val + 1) return search(rs(rt), mid + 1, r, mid + 1);//左子树全为 1 且 val <= mid,到右子树查询
return search(ls(rt), l, mid, val);//否则在左子树查询
}
inline bool cmp(int x, int y, int l, int r){
if(l == r) return t[x].sum < t[y].sum;
int mid = (l + r) >> 1;
if(t[rs(x)] == t[rs(y)]) return cmp(ls(x), ls(y), l, mid);
return cmp(rs(x), rs(y), mid + 1, r);
}
struct Que{
int id, rt;
friend bool operator < (Que a, Que b){
return cmp(b.rt, a.rt, 0, N - 1);
}
};
inline void dijkstra(){
priority_queue <Que> q;
for(int i = 1; i <= n; ++i)
root[i] = inf;
root[S] = root[0];
q.push((Que){S, root[S]});
while(!q.empty()){
int x = q.top().id;
q.pop();
if(vis[x]) continue;
vis[x] = 1;
for(int i = head[x]; i; i = edge[i].nxt){
int y = edge[i].v, rt_x = root[x], res = cnt;
int pos = search(root[x], 0, N - 1, edge[i].w);
update(rt_x, 0, N - 1, pos);
if(edge[i].w < pos) clear(rt_x, root[0], edge[i].w, pos - 1, 0, N - 1);
if(cmp(rt_x, root[y], 0, N - 1)) pre[y] = x, q.push((Que){y, root[y] = rt_x});
else cnt = res;
}
}
}
int main(){
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);
}
S = read(), T = read();
fac[0] = 1;
for(int i = 1; i < N; ++i)
fac[i] = fac[i - 1] * 2 % mod;
build(root[0], 0, N - 1, 0), build(inf, 0, N - 1, 1);
dijkstra();
if(root[T] == inf){
puts("-1");
return 0;
}
for(int x = T; x != S; x = pre[x]) ans[++path] = x;
ans[++path] = S;
printf("%lld\n%d\n", t[root[T]].sum, path);
for(int i = path; i >= 1; --i)
printf("%d ", ans[i]);
return 0;
}
\[\_EOF\_
\]