BZOJ 4046 [Cerc2014] Pork barre

题目大意:给你一堆边,询问边权在[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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值