學習筆記第二十六節:線段樹優化建圖
阿新 • • 發佈:2018-11-09
正題
這真是一個神奇的東西。
既然有這個演算法,那麼就一定有他能解決的題目。
我們以這一題為例:[POI2015]PUS。
給出n個數,m個操作,每次規定l到r中的k個數比這個區間的其他數大,詢問是否有解。
簡化問題,給出n個數,m個操作,每次規定第a個數比第b個數大。(還規定一些數的值
那麼很明顯是一個差分約束的問題。
每次建一條邊
最後入度為0的為起點,跑一次最長路,就是每個點最小權值,如果最小權值大於規定的值,那麼無解,如果有環,那麼無解。
如果最小權值大於1e9無解。(以上的最小權值指的是每一個點的最小權值
就是個拓撲序上bfs。
做完了。
轉化為原題,每次要連的邊為,所以邊就爆炸了。
想法一
我們可以建一個虛點x,從個點連到這個點,然後從這個點連向k個點,令第一組邊權為0,第二組的邊權為1,總邊數為
然而m個操作,爆炸。
優化
k個點把這個區間分成了k+1各區間?對。
每個區間向虛點連邊?對。
空間?爆炸。
怎麼優化?區間想到線段樹
我們可以建一棵線段樹,令兒子指向父親,邊權為0。然後線段樹可以使一個區間對應線段樹上的點。
那麼我們讓這
空間?線段樹永遠是2n條邊,每次log(n),一共次,從虛點連到k個點,一共要連k條邊,所以一共條邊。
加起來?
明顯不會爆炸。
做完了?對,其實判環這個功能拓撲排序本來就擁有。
#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]);
}