題目大意:給你一堆邊,詢問邊權在[a,b]的範圍內的能加入的最大邊數使圖無環的情況下使邊權和最小,點1000,邊100000,詢問1000000, 強制在線
把邊先按邊權排序,我們再來思考這個問題。
先想一下簡化版,假設所有詢問的左端點相同,那麼問題是不是就簡單一些了?左端點固定,從這個點開始跑最小生成樹,相當於只選最小生成樹的邊。對於第i條邊,權值爲Ci,如果在最小生成樹上,那麼對答案的貢獻就是Ci,如果不在最小生成樹上,那麼貢獻就是0(因爲肯定不會選這條邊)。然後查詢[a,b]這個區間我們只需要用線段樹求個和就好了。
那如果左端點在動呢?對於第i+1爲左端點我們已經搞定的時候,我們把第i條邊加入到第i+1條邊爲左端點的最優生成樹(或者還未構成一棵樹)中,就有兩種情況,設第i條邊的兩個端點分別爲u,v,第一種就是u,v不連通,那麼直接把第i條邊的權值加進去就好了,第二種就是u,v連通,那麼就把u,v之間最長的一條邊替換即可,再把第i條邊加進去就好了,替換的具體體現就是把其在線段樹上的值改爲0就行了。因爲每次查詢左端點不同,而我們的目標又是要固定左端點,所以要保存所有所有左端點的版本。因此可持久化線段樹即可。
關於如何找到u,v間最長的一條邊,LCT能做到O(logn)的時間複雜度,然而我比較懶。。。寫的暴力O(n)的。。勉強卡過
上面講的都是預處理部分,詢問直接查詢就好了。
時間複雜度O(MlogM+QlogM)(LCT版)
O(MN+QlogM)(暴力版)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 1005, M = 100005;
typedef long long ll;
template<class T>
inline void read(T &x) {char op;while(((op=getchar())<'0')||(op>'9'));x=op-'0';while(((op=getchar())>='0')&&(op<='9'))(x*=10)+=op-'0'; }
char buf[30];
template<class T>
inline void out(T x) {
int i = 0;
if(!x) putchar('0');
while(x) buf[++i] = x%10+'0', x/=10;
while(i) putchar(buf[i--]);
putchar('\n');
}
int n, m, q, cas, g[N], to[M<<1], idx[M<<1], next[M<<1], w[M<<1], Np, lc[M*50], rc[M*50], root[M], fa[N], pa[N], pid[N], np;
ll sum[M*50];
void push(int x,int y,int id,int c) { next[++Np]=g[x]; to[Np]=y; idx[Np]=id; g[x]=Np; w[Np] = c; }
void Init() {
Np = 1;np = 0;
memset(root,0,sizeof(root));
memset(g,0,sizeof(g));
memset(sum,0,sizeof(sum));
memset(lc,0,sizeof(lc));
memset(rc,0,sizeof(rc));
for(int i=1;i<=n;i++) fa[i] = i;
}
struct edge {
int u,v,c;
bool operator <(const edge &E)const { return c != E.c ? c < E.c : u < E.u; }
}E[M];
int find(int x) { return x == fa[x] ? x : (fa[x] = find(fa[x])); }
void merge(int x,int y) { fa[find(x)] = find(y); }
bool isconnect(int x,int y) { return find(x) == find(y); }
void pushup(int x) { sum[x] = sum[lc[x]]+sum[rc[x]]; }
void build(int &now,int i,int j) {
now = ++np;
if(i == j) {
sum[now] = 0;
return;
}
int m = (i+j)>>1;
build(lc[now],i,m);
build(rc[now],m+1,j);
pushup(now);
}
inline void copy(int now,int pre) { lc[now] = lc[pre]; rc[now] = rc[pre]; sum[now] = sum[pre]; }
inline void update(int &now,int pre,int i,int j,int x,int v) {
now = ++np;
copy(now,pre);
if(i == j) { sum[now] += v; return; }
int m = (i+j)>>1;
if(m>=x) update(lc[now],lc[pre],i,m,x,v);
else update(rc[now],rc[pre],m+1,j,x,v);
pushup(now);
}
ll query(int now,int i,int j,int x,int y) {
if(i>=x && j<=y) return sum[now];
int m = (i+j)>>1;
ll tt1 = 0, tt2 = 0;
if(m>=x) tt1 = query(lc[now],i,m,x,y);
if(m<y) tt2 = query(rc[now],m+1,j,x,y);
return tt1+tt2;
}
inline void findedge(int u,int v,int p,int len) {
queue<int>q;
memset(pa,0,sizeof(pa));
pa[u] = -1;
q.push(u);
while(!q.empty()) {
int x = q.front();q.pop();
for(int i=g[x];i;i=next[i]) {
int y = to[i];
if(pa[y]) continue;
pa[y] = x;
pid[y] = i;
q.push(y);
}
if(pa[v]) break;
}
int id = pid[v], x = v, last1 = pa[v], last2 = v;
while(x != u) {
if(w[pid[x]] > w[id]) {
id = pid[x];
last1 = pa[x];
last2 = x;
}
x = pa[x];
}
int id2 = id^1;
if(g[last2] == id2) g[last2] = next[id2];
for(int i=g[last2];next[i];i=next[i])
if(next[i] == id2) next[i] = next[next[i]];
if(g[last1] == id) g[last1] = next[id];
for(int i=g[last1];next[i];i=next[i])
if(next[i] == id) next[i] = next[next[i]];
update(root[p],root[p],1,m,idx[id],-w[id]);
}
int main() {
read(cas);
while(cas--) {
read(n);read(m);
for(int i=1;i<=m;i++) read(E[i].u), read(E[i].v), read(E[i].c);
sort(E+1,E+1+m);
Init();
build(root[m+1],1,m);
for(int i=m;i>=1;i--) {
root[i] = ++np;
update(root[i],root[i+1],1,m,i,E[i].c);
if(isconnect(E[i].u, E[i].v))
findedge(E[i].u,E[i].v,i,E[i].c);
else merge(E[i].u, E[i].v);
push(E[i].u,E[i].v,i,E[i].c);
push(E[i].v,E[i].u,i,E[i].c);
}
read(q);
int l, r;
long long lastans = 0;
for(int i=1;i<=q;i++) {
read(l);read(r);
l -= lastans; r -= lastans;
l = lower_bound(E+1,E+1+m,(edge){0,0,l})-E;
r = upper_bound(E+1,E+1+m,(edge){n+1,n+1,r})-E-1;
if(l > r) lastans = 0;
else lastans = query(root[l],1,m,l,r);
out(lastans);
}
}
return 0;
}