算法分析:
首先,有一个最简单的思路,把这个区间里的其它数向这 个数连一条边权为 的边,表示这 个数要比其它的数大,然后跑一边拓扑排序,求最长路。如果有环,那么问题就无解。否则,有解的图一定是一个有向无环图。
考虑为什么要求最长路。这里的最长路表示该点的最小取值。如果我们求出来的这个点的 大于给定数范围的限制,或者当前点是给定的,但我们求出的最长路大于他给定的值,那么就无解,否则直接输出从 到 的 即可。
但这道题的边数有 条, 是给定的 个点, 是其余的点。显然炸了。
考虑第一个优化,我们可以建一个虚拟点 ,把 向 个点连一条边权为 的边,然后把其它的区间向虚拟点 连一条边权为 的边,这样我们就可以表示上述所说的把这个区间里的其它数向这 个数连一条边权为 的边。
但边的条数我们还是不能接受。
考虑进一步的优化。我们有 个区间,每一个区间最多有 段。跟区间有关的连边,我们可以想到线段树优化建图。具体就是,线段树的两个孩子节点分别向父节点连一条边权为 的边,然后区间向虚拟点 连边就可以表示为线段树上的一段区间向虚拟点 连边。这样向每段区间最多有 条边,区间的总数是 ,线段树内部有 条边,给定的 个点需要连边,这样边的总数是 ,可以过这道题。
最后说一下如何判环,我们发现在拓扑排序中,把每一个点 如果访问到就 最后看是否有点没有被访问过,如果有的话那就是有环,没有的话直接判最长路即可。
总代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#define re register
#define rep(a,b,c) for(re int a(b) ; a<=c ; ++a)
#define drep(a,b,c) for(re int a(b) ; a>=c ; --a)
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48) ; ch=getchar();}
return x*f;
}
const int M = 5e5+10;
const int limit = 1e9;
int a[M],dis[M],head[M],vis[M];
int du[M],t[M];
int n,s,m;
int cnt,tot;
struct edge{
int to,nxt,w;
}e[M*10]; //注意不要开小
inline void add(int u,int v,int w){ //加边
e[++cnt].to = v;
e[cnt].w = w;
e[cnt].nxt = head[u];
head[u] = cnt;
du[v]++;
}
inline void build(int k,int l,int r){ //正常的线段树建立
if(l == r){
t[k] = l; //叶节点的编号
return;
}
t[k] = ++tot; //每一个节点的编号
int mid = (l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
add(t[k<<1],t[k],0),add(t[k<<1|1],t[k],0); //左右儿子分别向父亲连边
}
inline void link(int k,int l,int r,int x,int y,int node){
if(x<=l && r<=y){
add(t[k],node,0); // 这个区间向虚拟点node连边
return;
}
int mid = (l+r)>>1;
if(x<=mid) link(k<<1,l,mid,x,y,node);
if(y>mid) link(k<<1|1,mid+1,r,x,y,node); //正常操作
}
inline void topo(){
queue<int> q;
for(re int i(1) ; i<=tot ; ++i){
if(!dis[i]) dis[i] = 1; //从1开始
if(du[i] == 0) q.push(i); //把入度为0的点先加进队列
}
while(!q.empty()){
int u = q.front();
q.pop();
vis[u] = 1; //标记这个点走到了
for(re int i(head[u]) ; i ; i=e[i].nxt){
int v = e[i].to,w = e[i].w;
if(dis[v] < dis[u] + w) dis[v] = dis[u] + w; //更新最长路
if(a[v] && dis[v] > a[v]) printf("NIE\n"),exit(0); //如果不满足条件
du[v]--;
if(!du[v]) q.push(v);
}
}
}
signed main(){
n=read(),s=read(),m=read();
tot = n;
build(1,1,n);
rep(i,1,s){
int pos=read(),x=read();
a[pos] = dis[pos] = x;
}
rep(i,1,m){
int l=read(),r=read(),k=read();
tot++; //tot就是虚拟点
int pre = l-1; //上一段区间的左端点
int x;
for(re int j(1) ; j<=k ; ++j){
x = read();
add(tot,x,1); //虚拟点向x连边
if(x > pre+1) link(1,1,n,pre+1,x-1,tot); //这个区间没有被连边,就连边
pre = x; //更新pre
}
if(x < r) link(1,1,n,x+1,r,tot); //最后看是否会剩一段区间没有连边
}
topo();
for(re int i(1) ; i<=tot ; ++i) if(!vis[i] || dis[i] > limit) { printf("NIE\n"); return 0; } //判无解
printf("TAK\n");
rep(i,1,n) printf("%d ",dis[i]);
return 0;
}