P3588 [POI2015] PUS

      ~~~~~      P3588 [POI2015] PUS       ~~~~~      总题单链接

思路

      ~~~~~      这道题的关键点在于线段树优化建图。

      ~~~~~      对每条限制新建一个虚电 p p p,将输入的 x 1 ∼ k x_{1\sim k} x1k 连向 p p p,再将 p p p 连向区间内单的其他点,建完图后拓扑排序即可。

      ~~~~~      但如果每个区间都是 [ 1 , 100000 ] [1,100000] [1,100000] k = 1 k=1 k=1,那么就会连 ∑ k × n \sum k\times n k×n 条边。

      ~~~~~      怎么办,发挥想象力,用线段树优化建图。

      ~~~~~      每个限制的区间最多会被拆分成 k + 1 k+1 k+1 个区间,暴力枚举区间,用线段树建图即可。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;

vector<ll>eg[500005];

ll n,s,m,tot,usr;
ll vis[500005],din[500005],mx[500005];

class{
public:
	struct Point{
		ll L,R,id;
	}po[400005];
	void build(ll x,ll y,ll p=1){
		po[p]={x,y,++tot};
		if(x==y){
			eg[po[p].id].push_back(x),din[x]++;
			return;
		}
		ll mid=(x+y)>>1;
		build(x,mid,p<<1);
		build(mid+1,y,p<<1|1);
		eg[po[p].id].push_back(po[p<<1].id),din[po[p<<1].id]++;
		eg[po[p].id].push_back(po[p<<1|1].id),din[po[p<<1|1].id]++;
	}
	void query(ll wc,ll x,ll y,ll p=1){
		if(po[p].R<x||po[p].L>y)return;
		if(po[p].L>=x&&po[p].R<=y){
			eg[wc].push_back(po[p].id);din[po[p].id]++;
			return;
		}
		query(wc,x,y,p<<1);
		query(wc,x,y,p<<1|1);
	}
}tr;

queue<ll>que;
void tupo(){
	for(ll i=1;i<=tot;i++)
		if(!din[i])que.push(i);
	while(!que.empty()){
		ll u=que.front();que.pop();
		usr++;
		if(mx[u]<1||vis[u]>mx[u]){
			cout<<"NIE";
			exit(0);
		}
		for(ll v:eg[u]){
			din[v]--;
			if(u<=n)mx[v]=min(mx[v],mx[u]-1);
			else mx[v]=min(mx[v],mx[u]);
			if(din[v]==0)que.push(v);
		}
	}
	if(usr!=tot){
		cout<<"NIE";
		exit(0);
	}
}

vector<ll>now;
signed main(){
	ios::sync_with_stdio(false);
	
	cin>>n>>s>>m;tot=n;
	for(ll i=1;i<=s;i++){
		ll x,y;cin>>x>>y;
		vis[x]=y;
	}
	tr.build(1,n);
	for(ll q=1;q<=m;q++){
		ll x,y,k;cin>>x>>y>>k;
		now.clear();
		for(ll i=1;i<=k;i++){
			ll g;cin>>g;
			now.push_back(g);
		}
		tot++;
		if(now[0]-1>=x)tr.query(tot,x,now[0]-1);
		for(ll i=1;i<now.size();i++){
			if(now[i-1]+1>now[i]-1)continue;
			tr.query(tot,now[i-1]+1,now[i]-1);
		}
		for(ll it:now)eg[it].push_back(tot),din[tot]++;
		if(y>=now[now.size()-1]+1)tr.query(tot,now[now.size()-1]+1,y);
	}
	
	for(ll i=1;i<=tot;i++){
		if(vis[i])mx[i]=vis[i];
		else mx[i]=1000000000;
	}
	tupo();
	
	cout<<"TAK"<<endl;
	for(ll i=1;i<=n;i++)cout<<mx[i]<<" ";
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值