数据结构优化连边

什么是线段树连边

对于一类题目,它们建出来的图好大好大,这时候通常可以利用线段树(大多数时候是可持久化线段树)来进行优化连边,保证点的连通性不变,路径权值信息不变。

例题1:湖南集训2014JabberwockyII

前言:这道题当时是我在Noip吧问的,年代比较久远。
给出三个数列{Xi},{Ai},{Bi}。
若存在一个长度为m数列{Pi},满足P递增且P[1]=1,P[m]=n。
那么该数列P的权值sum按以下方式计算:
sum=0
for i = 2 to m
if(X[P[i]]<=X[P[i-1]])
then sum = sum + A[P[i-1]]
else sum = sum + B[P[i-1]]
问在所有满足条件的数列中,权值第k小的序列的权值。
n<=20001,k<=1000000。
显然,如果k为1的话,我们可以列出DP式。
设f[i,j]表示这个P数列的第j个是i时最小权值。
f[i,j]=mink<i(f[k,j1]+(x[i]<=x[k])?a[k]:b[k])
那么现在,对于k小,要怎么解决呢?总不可能设多一维来表示第几小吧?
我们注意到,可以对于任意 i<j 且x[i]<=x[j],i连j权值为a[i]的有向边,若x[i]>x[j]就连权值为b[i]的有向边。
那么现在只需要跑1到n的K短路就好了。
可是,数据大小不容许我们暴力建出图来,边的数量会达到n^2!
这是,我们可以考虑使用可持久化线段树优化连边。
设Ti表示为1~i的x数组组成的线段树(可以看做一个桶用线段树维护),那么i的入边来自Ti-1,而我们把前i-1个点分成了log n组,所以只有log n条边。然后相邻两颗线段树之间的对应区间(不同的那log n个)再连条边。我们就成功用可持久化线段树优化了建图,边的数量只有n log n。

例题2:游行

前言:这是philipsweng大神出的题目。
恶梦是学校里面的学生会主席。他今天非常的兴奋,因为学校一年一度的学生节开始啦!!
在这次节日上总共有N个节目,并且总共也有N个舞台供大家表演。其中第i个节目的表演时间为第i个单位时间,表演的舞台为Ai,注意可能有多个节目使用同一个舞台。
作为恶梦的忠实粉丝之一的肥佬,当然要来逛一下啦,顺便看一下能不能要到恶梦的签名。
肥佬一开始会先在A1 看完节目1再去闲逛。
肥佬可以在舞台之间随便乱走。但是假如肥佬当前在看第i个节目,站在第Ai个舞台前面的话,由于有些道路被封锁了,所以肥佬下一步只能前往第Li到第Ri个舞台中的一个。并且当一个节目结束的时候,肥佬只能去看另外一个节目,或者结束自己的闲逛。
具体而言就是说,假设肥佬可以从第i个节目走去第j个节目,那么当且仅当 i<j 且Li <= Aj <= Ri。
但事实上是,恶梦非常讨厌被自己的粉丝跟踪。所以他想在只封锁掉一个节目的情况下,使得肥佬不能到达自己所在的地方。并且为了防止意外,他还想知道有多少个这样的节目。
简而言之,恶梦想知道对于任意一个节目p∈[1,N],有多少个节目t,使得删掉t之后,不存在一条从节目1出发到节目p的路径。注意,节目1和节目p也是可以被删的。
由于他非常的忙碌,所以他把这个任务交给了你。
N <= 50000。
我们考虑暴力连边,建出图以后,我们可以用dominator tree来做(这是一个DAG,所以不需要用到lengauer tarjan算法,可以直接倍增建树)。

#include<cstdio>
#include<algorithm>
#include<deque>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=5000+10;
deque<int> dl;
int d[maxn],f[maxn][15],r[maxn],a[maxn],h[maxn],go[1000000],next[1000000],ans[maxn];
//int from[maxn*maxn];
int i,j,k,l,t,n,m,now,tot;
void add(int x,int y){
    go[++tot]=y;
    //from[tot]=x;
    next[tot]=h[x];
    h[x]=tot;
    r[y]++;
}
int lca(int x,int y){
    if (d[x]<d[y]) swap(x,y);
    int j;
    if (d[x]!=d[y]){
        j=floor(log(n)/log(2));
        while (j>=0){
            if (d[f[x][j]]>=d[y]) x=f[x][j];
            j--;
        }
    }
    if (x==y) return x;
    j=floor(log(n)/log(2));
    while (j>=0){
        if (f[x][j]!=f[y][j]){
            x=f[x][j];
            y=f[y][j];
        }
        j--;
    }
    return f[x][0];
}
int main(){
    scanf("%d",&n);
    fo(i,1,n) scanf("%d",&a[i]);
    fo(i,1,n){
        scanf("%d%d",&k,&l);
        fo(j,i+1,n)
            if (k<=a[j]&&a[j]<=l) add(i,j);
    }
    //fo(i,1,tot) printf("%d %d\n",from[i],go[i]);
    fo(i,2,n) 
        if (!r[i]){
            t=h[i];
            while (t){
                r[go[t]]--;
                t=next[t];
            }
        }
    dl.push_back(1);
    d[1]=1;
    while (!dl.empty()){
        now=dl.front();
        dl.pop_front();
        d[now]=d[f[now][0]]+1;
        fo(i,1,floor(log(n)/log(2))) f[now][i]=f[f[now][i-1]][i-1];
        t=h[now];
        while (t){
            j=go[t];
            if (!f[j][0]) f[j][0]=now;else f[j][0]=lca(f[j][0],now);
            r[j]--;
            if (!r[j]) dl.push_back(j);
            t=next[t];
        }
    }
    fo(i,1,n){
        ans[i]=(d[i])?d[i]:-1;
        printf("%d\n",ans[i]);
    }
}

可是暴力建图显然不行。
可以参考例题1,那么我们可以使用可持久化线段树优化连边,再用dominator tree跑就好了。

例题3:Arietta

前言:第一次在别的地方做比赛(contest hunter),遇上了这题。当时打网络流结果只得到了爆搜分(水10分虐酸神)。
Arietta 的命运与她的妹妹不同,在她的妹妹已经走进学院的时候,她仍然留在山村中。
但是她从未停止过和恋人 Velding 的书信往来。一天,她准备去探访他。
对着窗外的阳光,临行前她再次弹起了琴。
她的琴的发声十分特殊。
让我们给一个形式化的定义吧。
所有的 n 个音符形成一棵由音符 C ( 1 号节点) 构成的有根树,每一个音符有一个音高 H_i 。
Arietta 有 m 个力度,第 i 个力度能弹出 D_i 节点的子树中,音高在 [L_i,R_i] 中的任意一个音符。
为了乐曲的和谐,Arietta 最多会弹奏第 i 个力度 T_i 次。
Arietta 想知道她最多能弹出多少个音符。
1 ≤ n, m ≤ 10000 。
1 ≤ H_i , T_i , P_i ≤ n, 1 ≤ L_i ≤ R_i ≤ n 。
显然可以网络流建图。
然后咧,我们容易想到弄出dfs序,然后用线段树套线段树/平衡树来优化连边。
题解说,因为是维护子树内的元素,所以用线段树合并来优化建图,注意要对线段树进行可持久化(出题人dwj后来说这是可以被卡的,因为线段树合并在叶节点时会加一层,所以如果所有点的点权都相同就会有N层然后就。。。)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值