这真是一个神奇的东西。
既然有这个算法,那么就一定有他能解决的题目。
我们以这一题为例:[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
#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]);
}