题意:
给定一个长度为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个数大(严格大于,即没有等号)。请任意构造出一组满足条件的方案,或者判断无解。
n,s,m(1<=s<=n<=100000,1<=m<=200000)
Σk <= 300,000
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<vector>
#define N 310000
#define inf 1e9
#define pb push_back
using namespace std;
struct node{int y,nex;}a[N];
struct node1{int l,r;};
struct node2{int l,r,lc,rc,p,mi,lz,lim;}lt[N];
int A[N],tl,n,m,du[N],fir[N],len,lim[N],ans[N];
vector<node1> v[N];
void ins(int x,int y)
{
a[++len].y=y;a[len].nex=fir[x];fir[x]=len;
}
void bt(int l,int r)
{
int now=++tl;
lt[now].l=l;lt[now].r=r;lt[now].lim=inf;
lt[now].p=l;
if(l<r)
{
int mid=(l+r)/2;
lt[now].lc=tl+1;bt(l,mid);
lt[now].rc=tl+1;bt(mid+1,r);
}
else
{
if(A[l]!=-1) lt[now].lim=A[l];
else lt[now].lim=inf;
}
}
void down(int now)
{
int lc=lt[now].lc,rc=lt[now].rc;
if(lc) lt[lc].lz+=lt[now].lz;
if(rc) lt[rc].lz+=lt[now].lz;
lt[now].mi+=lt[now].lz;
lt[now].lz=0;
}
void upd(int now)
{
int lc=lt[now].lc,rc=lt[now].rc;
if(lt[lc].lz) down(lc);
if(lt[rc].lz) down(rc);
if(lt[lc].mi<lt[rc].mi) lt[now].mi=lt[lc].mi,lt[now].p=lt[lc].p;
else lt[now].mi=lt[rc].mi,lt[now].p=lt[rc].p;
}
void change(int now,int l,int r,int o,int d)
{
int mid=(lt[now].l+lt[now].r)/2,lc=lt[now].lc,rc=lt[now].rc;
if(lt[now].lz) down(now);
if(lt[now].l==l && lt[now].r==r)
{lt[now].lz+=o;lt[now].lim=min(lt[now].lim,d);return;}
if(mid>=r) change(lc,l,r,o,d);
else if(l>mid) change(rc,l,r,o,d);
else change(lc,l,mid,o,d),change(rc,mid+1,r,o,d);
upd(now);
}
void init()
{
int s;scanf("%d%d%d",&n,&s,&m);
for(int i=1;i<=n;i++) A[i]=-1;
for(int i=1;i<=s;i++)
{
int x;scanf("%d",&x);
scanf("%d",&A[x]);
}
bt(1,n);
for(int i=1;i<=m;i++) lim[i]=inf;
for(int i=1;i<=m;i++)
{
int k,l,r,pre;scanf("%d%d%d",&l,&r,&k);
pre=l;
du[i]=k;
while(k--)
{
int x;scanf("%d",&x);
ins(x,i);
if(pre<x)
{
v[i].pb((node1){pre,x-1});
change(1,pre,x-1,1,inf);
}
pre=x+1;
}
if(pre<=r)
{
v[i].pb((node1){pre,r});
change(1,pre,r,1,inf);
}
}
}
int find(int now,int k)
{
int mid=(lt[now].l+lt[now].r)/2,lc=lt[now].lc,rc=lt[now].rc;
if(lt[now].lz) down(now);
if(lt[now].l==lt[now].r) return lt[now].lim;
if(mid>=k) return min(find(lc,k),lt[now].lim);
else return min(find(rc,k),lt[now].lim);
}
void make(int x)
{
lim[x]--;
int siz=v[x].size();
for(int i=0;i<siz;i++)
change(1,v[x][i].l,v[x][i].r,-1,lim[x]);
}
void solve()
{
int cnt=0;
while(cnt<n)
{
if(lt[1].mi>0) {printf("NIE\n");return;}
int x=lt[1].p;
int d=find(1,x);
if(d<=0) {printf("NIE\n");return;}
change(1,x,x,inf,inf);
if(A[x]!=-1 && d!=A[x]) {printf("NIE\n");return;}
ans[x]=d;
for(int k=fir[x];k;k=a[k].nex)
{
int y=a[k].y;
lim[y]=min(lim[y],d);
du[y]--;
if(du[y]==0)
{
if(v[y].size()==0) continue;
if(lim[y]<=1) {printf("NIE\n");return;}
make(y);
}
}
cnt++;
}
printf("TAK\n");
for(int i=1;i<=n;i++) printf("%d ",ans[i]);
}
int main()
{
init();
solve();
return 0;
}
题解:
注意这题的本质是找一个合法拓扑序。
对于每个限制(k,l,r)我们给他新建一个点,让这k个人指向这个点,这个点再指向其余所有点,跑拓扑即可。暴力连边有O(nm)条边,注意新点向[l,r]之中O(k)个连续的区间连边,于是可以用线段树区间加减解决。线段树好慢啊
我的做法复杂了些,看题解都直接在线段树上建边,拿线段树的点跑拓扑。