题解 P3588 【[POI2015]PUS】
我们在做题之前,先要知道这是一道图论题
题意描述有些绕,我在这里再阐述一遍:
- 给出一个部分未知的数列的长,以及数列已知的部分
- 再给出一些区间。对于每一个区间,在它的内部钦定一些位置,并要求这些位置上的数最后的值,都严格大于区间内其他未钦定的位置上的数。
- 要求给出任意一种可行的满足条件的数列。
大于关系可以看做是一条边,由较大的数指向较小的数。对于每一个询问,我们考虑让这k个位置上的数与区间内其他的位置两两连有向边。这样一来,问题就转化到图上了。
首先,图上若有环则一定无解,因为它意味着
那么问题变为DAG上问题,只要我们从入读为0的位置开始DP即可。贪心的想,最大的数越大,留给下面的空间就越大(注意数列中的数大于0!),所以每个数的可能值全部设置为1e9。DP时假如遇到了已经确定的数,却发现它撑死了也打不到他预设的值(否则与其他单调关系矛盾),那么问题无解。
最后若有解,输出答案即可。
好完美啊,在你看到
假如按之前说的那样建图,一次操作就要加入
也许你会说,我们可以采用“电话交换机”的建图思想,不再两两连边
而是新拉出一个超级节点,然后只需要k个点向它连边,它向区间内其余点连边即可,只要
然而这还是不行。因为如果每个操作区间都是上十万的大区间,而k每次只有1,最多还是要连
注意到,每次我们的空间浪费在,有大量位置连续的点,超级节点却向它们一一连了边。发挥想象力,有什么东西能提高区间操作的效率呢?
线段树!!!
没错,我们把数列建成一棵线段树,然后对于每个操作区间,它会被k个点割成最多k+1个子区间,对于每个区间,可以化成线段树上的最多
这样连边以后,要注意线段树自身的边只是形式,要赋权为0。
之后按照之前所说的,按拓扑序DP就行了。
#include<iostream>
#include<cstdio>
#include<queue>
#define lson ls[u],l,md
#define rson rs[u],md+1,r
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define REP(u) for(int i=hd[u],v=e[i].v,w=e[i].w;i;i=e[i].n,v=e[i].v,w=e[i].w)
using namespace std;
const int N=100100,TO=600600,M=7007000,INF=1000000000;
struct edge{int n,v,w;}e[M];
int n,s,m,p,v,u,x,k,l,r,pr,cnt,fl,ok,id[N],ins[TO];
int hd[TO],vis[TO],in[TO],f[TO],ls[TO],rs[TO],o[TO];
void add(int u,int v,int w){e[++fl]=(edge){hd[u],v,w};hd[u]=fl;in[v]++;}
queue<int>q;
int read(){
char ch=getchar();int x=0,o=1;
for(;ch<'0' || '9'<ch;ch=getchar()) if(ch=='-') o=-1;
for(;'0'<=ch&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch^'0');
return x*o;
}
void prg(){
FOR(x,1,cnt){
cout<<x<<':';
REP(x) cout<<v<<'-'<<w<<' ';cout<<endl;
}
}
void bld(int u,int l,int r){
if(l==r) {id[l]=u;cnt=max(cnt,u);return;}
int md=l+r>>1;
ls[u]=++cnt,rs[u]=++cnt;
bld(lson),bld(rson);
add(u,ls[u],0),add(u,rs[u],0);
}
void adde(int u,int l,int r,int x,int y,int z){
if(x<=l && r<=y) {add(z,u,1);return;}
if( y<l || r<x ) return;
int md=l+r>>1;
adde(lson,x,y,z),adde(rson,x,y,z);
}
void dfs(int u){
vis[u]=1;ins[u]=1;
REP(u){
if(ins[v]) ok=0;
if(!vis[v]) dfs(v);
}
ins[u]=0;
}
int main(){
scanf("%d%d%d",&n,&s,&m);
cnt=1,bld(1,1,n);
FOR(i,1,s) p=read(),f[id[p]]=read(),o[id[p]]=f[id[p]];
FOR(i,1,m){
l=read(),r=read(),k=read();
pr=l;++cnt;
FOR(j,1,k){
x=read();
add(id[x],cnt,0);
if(pr<=x-1) adde(1,1,n,pr,x-1,cnt);
pr=x+1;
}if(pr<=r) adde(1,1,n,pr,r,cnt);
}
FOR(i,1,cnt) if(!f[i]) f[i]=INF;
FOR(i,1,cnt) if(!vis[i]){
ok=1;dfs(i);
if(!ok){printf("NIE");return 0;}
}
FOR(i,1,cnt) if(!in[i]) q.push(i);
while(!q.empty()){
u=q.front();q.pop();
REP(u){
in[v]--;
if(o[v]>f[u]-w){printf("NIE");return 0;}
f[v]=min(f[v],f[u]-w);
if(!in[v]) q.push(v);
if(f[v]<1){printf("NIE");return 0;}
}
}
printf("TAK\n");
FOR(i,1,n) printf("%d ",f[id[i]]);
}