1. 程式人生 > >學習筆記第二十六節:線段樹優化建圖

學習筆記第二十六節:線段樹優化建圖

正題

      這真是一個神奇的東西。

      既然有這個演算法,那麼就一定有他能解決的題目。

      我們以這一題為例:[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<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<queue>
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<int> 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(mid<x) get_sub((now<<1)|1,x,y,mid+1,r);
    else get_sub(now<<1,x,mid,l,mid),get_sub((now<<1)|1,mid+1,y,mid+1,r);
}

void Tp(){
    for(int i=1;i<=tot;i++){
        if(!dis[i])dis[i]=1;
        if(!in[i]) f.push(i);
    }
    while(!f.empty()){
        int x=f.front();f.pop();
        for(int i=first[x];i!=0;i=s[i].next){
            int y=s[i].y;
            in[y]--;
            dis[y]=max(dis[y],dis[x]+s[i].c);
            if(a[y] && dis[y]>a[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]);
}