版权声明:因为我是蒟蒻,所以请大佬和神犇们不要转载(有坑)的文章,并指出问题,谢谢 https://blog.csdn.net/Deep_Kevin/article/details/83867830
正题
这真是一个神奇的东西。
既然有这个算法,那么就一定有他能解决的题目。
我们以这一题为例:[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]);
}