学习笔记第二十六节:线段树优化建图

正题

      这真是一个神奇的东西。

      既然有这个算法,那么就一定有他能解决的题目。

      我们以这一题为例:[POI2015]PUS。

      给出n个数,m个操作,每次规定l到r中的k个数比这个区间的其他数大,询问是否有解。

      简化问题,给出n个数,m个操作,每次规定第a个数比第b个数大。(还规定一些数的值

      那么很明显是一个差分约束的问题。

      每次建一条边b\to a

      最后入度为0的为起点,跑一次最长路,就是每个点最小权值,如果最小权值大于规定的值,那么无解,如果有环,那么无解。

      如果最小权值大于1e9无解。(以上的最小权值指的是每一个点的最小权值

      就是个拓扑序上bfs。

      做完了。

      转化为原题,每次要连的边为(r-l+1-k)*k,所以边就爆炸了。

想法一

      我们可以建一个虚点x,从(r-l+1-k)个点连到这个点,然后从这个点连向k个点,令第一组边权为0,第二组的边权为1,总边数为r-l+1。好优秀啊。

      然而m个操作,爆炸。

优化

      k个点把这个区间分成了k+1各区间?对。

      每个区间向虚点连边?对。

      空间?爆炸。

      怎么优化?区间\to想到线段树

       我们可以建一棵线段树,令儿子指向父亲,边权为0。然后线段树可以使一个区间对应线段树上的log(n)点。

       那么我们让这log(n)个点向虚点连边就可以了。

       空间?线段树永远是2n条边,每次log(n),一共\sum_{i=1}^{m}(k_i+1)次,从虚点连到k个点,一共要连k条边,所以一共\sum k条边。

       加起来?2n+log(n)*\sum k+\sum k+m=???

       明显不会爆炸。

       做完了?对,其实判环这个功能拓扑排序本来就拥有。

#include
#include
#include
#include
#include
using namespace std;

int n,t,m;
int a[500010],dis[500010];
struct edge{
    int y,next,c;
}s[6000010];
int op[500010];
int first[500010],len=0,in[500010];
int tot;
queue f;

void ins(int x,int y,int c){
    len++;
    s[len]=(edge){y,first[x],c};first[x]=len;
    in[y]++;
}

void build_tr(int now,int l,int r){
    if(l==r) {op[now]=l;return ;}
    int mid=(l+r)/2;
    op[now]=++tot;
    build_tr(now<<1,l,mid);
    build_tr((now<<1)|1,mid+1,r);
    ins(op[now<<1],op[now],0);
    ins(op[(now<<1)|1],op[now],0);
}

void get_sub(int now,int x,int y,int l,int r){
    if(x==l && y==r) {ins(op[now],tot,0);return ;}
    int mid=(l+r)/2;
    if(y<=mid) get_sub(now<<1,x,y,l,mid);
    else if(mida[y]) {
                printf("NIE");exit(0);
            }
            if(!in[y]) f.push(y);
        }
    }
}

int main(){
    scanf("%d %d %d",&n,&t,&m);tot=n;
    int l,r,k,x,last;
    for(int i=1;i<=t;i++) scanf("%d %d",&x,&last),dis[x]=a[x]=last;
    build_tr(1,1,n);
    for(int i=1;i<=m;i++){
        scanf("%d %d %d",&l,&r,&k);
        last=l-1;tot++;
        for(int j=1;j<=k;j++){
            scanf("%d",&x);
            ins(tot,x,1);
            if(last+1<=x-1) get_sub(1,last+1,x-1,1,n);
            last=x;
        }
        if(last+1<=r) get_sub(1,last+1,r,1,n);
    }
    Tp();
    for(int i=1;i<=tot;i++)
        if(in[i] ||dis[i]>1e9){
            printf("NIE");
            return 0;
        }
    printf("TAK\n");
    for(int i=1;i<=n;i++) printf("%d ",dis[i]);
}

      

你可能感兴趣的:(学习笔记)