BZOJ4383[POI2015] Pustynia
Description
给定一个长度为n的正整数序列a,每个数都在1到10^9范围内,告诉你其中s个数,并给出m条信息,每条信息包含三个数l,r,k以及接下来k个正整数,表示a[l],a[l+1],…,a[r-1],a[r]里这k个数中的任意一个都比任意一个剩下的r-l+1-k个数大(严格大于,即没有等号)。
请任意构造出一组满足条件的方案,或者判断无解。
Input
第一行包含三个正整数n,s,m(1<=s<=n<=100000,1<=m<=200000)
接下来s行,每行包含两个正整数p[i],di,表示已知a[p[i]]=d[i],保证p[i]递增。
接下来m行,每行一开始为三个正整数l[i],r[i],ki,接下来k[i]个正整数 x[1],x[2],...,x[k[i]](l[i]<=x[1]<x[2]<...<x[k[i]]<=r[i]) ,表示这k[i]个数中的任意一个都比任意一个剩下的r[i]-l[i]+1-k[i]个数大。 Σ k <= 300,000
Output
若无解,则输出NIE。
否则第一行输出TAK,第二行输出n个正整数,依次输出序列a中每个数。
Sample Input
5 2 2
2 7
5 3
1 4 2 2 3
4 5 1 4
Sample Output
TAK
6 7 1000000000 6 3
Solution:
首先,这种元素之间比较大小的题目应该很快就能归到图论上,建一条有向边
于是就有一个非常经典的想法:建立虚点。
很明显可以看到,虚点的建立将边从 O(n2) 级别降到 O(n) 级别。
然而,建边的复杂度仍然在 O(n2) 规模。还要想办法优化。
题目还给了一个条件: Σ k <= 300,000
可以发现:每次都是把
进行如图所示的连接,就可以完成建边了。由于在线段树上更新的复杂度是
#include<stdio.h>
#include<ctype.h>
#include<string.h>
#define M 200005
#define Max 1000000000
#define Exit {puts("NIE");return 0;}
void Rd(int &res){
char c;res=0;
while(c=getchar(),!isdigit(c));
do{
res=(res<<1)+(res<<3)+(c^48);
}while(c=getchar(),isdigit(c));
}
struct Node{int to,w,nxt;}Edge[M<<3];
int tot=0,Lson[M<<2],Rson[M<<2],tot_edge=0,Head[M<<2],degree[M<<2],pos[M],Q[M<<2],A[M<<2],dp[M<<2];
void Addedge(int a,int b,int z){
Edge[++tot_edge].to=b;Edge[tot_edge].w=z;Edge[tot_edge].nxt=Head[a];Head[a]=tot_edge;degree[b]++;
}
int Build(int L,int R){
int p=++tot;
if(L==R){
pos[L]=p;
return p;
}
int mid=(L+R)>>1;
Lson[p]=Build(L,mid);
Addedge(Lson[p],p,0);
Rson[p]=Build(mid+1,R);
Addedge(Rson[p],p,0);
return p;
}
void Link(int L,int R,int l,int r,int to,int p){
if(L==l&&R==r){
Addedge(p,to,0);
return;
}
int mid=(L+R)>>1;
if(mid>=r)Link(L,mid,l,r,to,Lson[p]);
else if(mid<l)Link(mid+1,R,l,r,to,Rson[p]);
else Link(L,mid,l,mid,to,Lson[p]),Link(mid+1,R,mid+1,r,to,Rson[p]);
}
void check(int &a,int b){
if(b>a)a=b;
}
int main(){
int n,s,m;
Rd(n);Rd(s);Rd(m);
memset(Head,-1,sizeof(Head));
memset(A,-1,sizeof(A));
Build(1,n);
for(int i=1;i<=s;i++){
int a,x;
Rd(a);Rd(x);
A[pos[a]]=x;
}
while(m--){
int p=++tot,L,R,k,last,x;
Rd(L);Rd(R);Rd(k);last=L-1;
while(k--){
Rd(x);
Addedge(p,pos[x],1);
if(x>last+1)Link(1,n,last+1,x-1,p,1);
last=x;
}
if(last<R)Link(1,n,last+1,R,p,1);
}
int L=1,R=0;
for(int i=1;i<=tot;i++){
if(!degree[i])Q[++R]=i,dp[i]=1;
if(~A[i])dp[i]=A[i];
}
while(L<=R){
int now=Q[L++];
for(int i=Head[now];~i;i=Edge[i].nxt){
int to=Edge[i].to;
degree[to]--;
check(dp[to],dp[now]+Edge[i].w);
if(dp[to]>Max)Exit;
if(!degree[to])Q[++R]=to;
}
}
if(R<tot)Exit;
for(int i=1;i<=tot;i++)
if(~A[i]&&dp[i]>A[i])Exit;
puts("TAK");
for(int i=1;i<=n;i++)
printf("%d%c",dp[pos[i]]," \n"[i==n]);
return 0;
}