[题解] T‘ill It‘s Over

前言

线段树+网络最大流的建模题。

题目链接

题目大意

最初时有 n n n 1 1 1 。给定 o p op op l l l ,其中, l l l 为操作次数上限。你有四个操作:

  1. o p = 1 op=1 op=1 ,则接下来两个整数 a , b a,b a,b ,表示可以将 a a a 变为 b b b
  2. o p = 2 op=2 op=2 ,则接下来三个整数 a 1 , a 2 , b 1 a_1,a_2,b_1 a1,a2,b1 ,表示可以将范围在 a 1 a_1 a1 a 2 a_2 a2 的任意的数变为 b 1 b_1 b1
  3. o p = 3 op=3 op=3 ,则接下来三个整数 a 1 , b 1 , b 2 a_1,b_1,b_2 a1,b1,b2 ,表示可以将 a 1 a_1 a1 变为范围在 b 1 b_1 b1 b 2 b_2 b2 的任意的数。
  4. o p = 4 op=4 op=4 ,则接下来四个整数 a 1 , a 2 , b 1 , b 2 a_1,a_2,b_1,b_2 a1,a2,b1,b2 ,表示可以将范围在 a 1 a_1 a1 a 2 a_2 a2 的任意的数变为范围在 b 1 b_1 b1 b 2 b_2 b2 的任意的数。

问最后能有多少个数字变为 k k k ,其中 1 < = a , b , a 1 , b 1 , a 2 , b 2 < = k 1<=a,b,a1,b1,a2,b2<=k 1<=a,b,a1,b1,a2,b2<=k

思路

首先用暴力建图跑网络流。如果看出使用网络流,则建图就变得非常简单了。

把所有单个数字的查询都看为一个区间,那么四个操作都将是区间的连边。

使用一个类似于中转站的两个节点记为 t m p 1 tmp1 tmp1 t m p 2 tmp2 tmp2 ,将 a a a 区间的所有值连向 t m p 1 tmp1 tmp1 ,将 t m p 2 tmp2 tmp2 连向 b b b 区间的所有值,容量为无穷大。则 t m p 1 tmp1 tmp1 t m p 2 tmp2 tmp2 连一条容量为 l l l 的边用来限制操作次数。

跑最大流即可得出答案。暴力建图伪代码:

int tot = k;
int opt, l;
for(int i = 1; i <= m; i++) {
	scanf("%d %d", &opt, &l);
	if(opt == 1) {
		int a, b;
		scanf("%d %d", &a, &b);
		Addedge(a, b, l);
	}
	else if(opt == 2) {
		int a1, a2, b1;
		scanf("%d %d %d", &a1, &a2, &b1);
		int tmp1 = ++tot;
		int tmp2 = ++tot;
		for(int i = a1; i <= a2; i++)
			Addedge(i, tmp1, INF);
		Addedge(tmp2, b1, INF);
		Addedge(tmp1, tmp2, l);
	}
	else if(opt == 3) {
		int a1, b1, b2;
		scanf("%d %d %d", &a1, &b1, &b2);
		int tmp1 = ++tot;
		int tmp2 = ++tot;
		Addedge(a1, tmp1, INF);
		for(int i = b1; i <= b2; i++)
			Addedge(tmp2, i, INF);
		Addedge(tmp1, tmp2, l);
	}
	else {
		int a1, a2, b1, b2;
		scanf("%d %d %d %d", &a1, &a2, &b1, &b2);
		int tmp1 = ++tot;
		int tmp2 = ++tot;
		for(int i = a1; i <= a2; i++)
			Addedge(i, tmp1, INF);
		for(int i = b1; i <= b2; i++)
			Addedge(tmp2, i, INF);
		Addedge(tmp1, tmp2, l);
	}
}
t = tot + 1;
Addedge(s, 1, n);
Addedge(k, t, INF);

显然,一次操作会产生最多 2 k + 2 2k+2 2k+2 条边,在观察这个数据范围,过不了。亲测 50 Pts。可能是常数太大。。。

由于是区间操作,可以想到使用数据结构来优化建图。

使用线段树,建立两颗线段树,如下图。
在这里插入图片描述
s s s 连向第一棵线段树的 [ 1 , 1 ] [1,1] [1,1] 区间的点,容量为 n n n ,第二棵线段树的 [ k , k ] [k,k] [k,k] 区间的点连向 t t t ,容量为极大值,和暴力差不多。

可以把第二棵树理解为是操作树,是用来进行操作的。第一棵树的儿子连向自己的父亲,方便选定被操作前的范围。第二棵的父亲连向自己的儿子,方便选定操作后的范围。以上边的容量均为极大值。

最后第二棵树的节点连向第一棵树的对应点,方便操作后被再次操作。

最后跑一边最大流 Dinic ,在随机图上 Dinic 普遍优于其他 O ( n m l o g ( m ) ) O(nmlog(m)) O(nmlog(m)) 的算法。

Code

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
const int MAXN = 1e6 + 5;
const int MAXM = 5e6 + 5;
struct Segment_Tree {
	int Left_Section, Right_Section, Data;
	#define LC(x) (x << 1)
	#define RC(x) (x << 1 | 1)
	#define L(x) tree[0][x].Left_Section
	#define R(x) tree[0][x].Right_Section
	#define D(x, y) tree[y][x].Data
};
Segment_Tree tree[2][MAXN];
struct Edge { 
	int Next, To, Cap;
};
Edge edge[MAXM << 1];
int head[MAXM << 1];
int edgetot = 1;
int tot;
int n, m, k, s, t;
queue<int> q;
int dep[MAXN], stt[MAXN];
int Begin, End;
void Addedge(int x, int y, int z) {
	edge[++edgetot].Next = head[x], edge[edgetot].To = y, edge[edgetot].Cap = z, head[x] = edgetot;
	edge[++edgetot].Next = head[y], edge[edgetot].To = x, edge[edgetot].Cap = 0, head[y] = edgetot;
}
void Build(int pos, int l, int r, int flag) {//初始化线段树的节点信息 
	D(pos, flag) = ++tot;//开辟新的节点 
	L(pos) = l;//初始化左区间 
	R(pos) = r;//初始化右区间 
	if(l == r) {
		if(flag && l == 1)//记录左树的1节点 
			Begin = D(pos, flag);
		if(!flag && l == k)//记录右树的k节点
			End = D(pos, flag);
		if(!flag)//右树连左树的对应节点 
			Addedge(D(pos, flag), D(pos, 1 - flag), INF);
		return;
	}
	int mid = (l + r) >> 1;
	Build(LC(pos), l, mid, flag);//初始化 
	Build(RC(pos), mid + 1, r, flag);//同上 
	if(flag) {//左树儿子连父亲 
		Addedge(D(LC(pos), flag), D(pos, flag), INF);
		Addedge(D(RC(pos), flag), D(pos, flag), INF);
	}
	else {//右树父亲连儿子 
		Addedge(D(pos, flag), D(LC(pos), flag), INF);
		Addedge(D(pos, flag), D(RC(pos), flag), INF);
	}
}
void Query(int pos, int l, int r, int tmp, int flag) {//区间连边 
	if(l <= L(pos) && R(pos) <= r) {
		if(flag)
			Addedge(D(pos, flag), tmp, INF);//若是左树则连接中转站 
		else
			Addedge(tmp, D(pos, flag), INF);//若是右树被中转站连接 
		return;
	}
	if(l <= R(LC(pos)))
		Query(LC(pos), l, r, tmp, flag);//处理子树 
	if(r >= L(RC(pos)))
		Query(RC(pos), l, r, tmp, flag);//同上 
}
bool bfs() {//Dinic板子,不详写 
	for(int i = s; i <= t; i++)
		dep[i] = 0;
	stt[s] = head[s];
	dep[s] = 1;
	q.push(s);
	while(!q.empty()) {
		int u = q.front(); q.pop();
		for(int i = head[u]; i; i = edge[i].Next) {
			int v = edge[i].To;
			if(!dep[v] && edge[i].Cap) {
				dep[v] = dep[u] + 1;
				stt[v] = head[v];
				q.push(v);
			}
		}
	}
	return dep[t] != 0;
}
int dfs(int u, int flow) {//同上 
	if(u == t || !flow)
		return flow;
	int rest = flow;
	for(int i = stt[u]; i && rest; i = edge[i].Next) {
		stt[u] = i;
		int v = edge[i].To;
		if(dep[v] == dep[u] + 1 && edge[i].Cap) {
			int nextflow = dfs(v, min(rest, edge[i].Cap));
			if(!nextflow)
				dep[v] = -1;
			edge[i].Cap -= nextflow;
			edge[i ^ 1].Cap += nextflow;
			rest -= nextflow;
		}
	}
	return flow - rest;
}
int Dinic() {//同上 
	int res = 0;
	int flow;
	while(bfs())
		while(flow = dfs(s, INF))
			res += flow;
	return res;
}
int main() {
	scanf("%d %d %d", &n, &m, &k);
	Build(1, 1, k, 1); 
	Build(1, 1, k, 0);
	int opt, l;
	for(int i = 1; i <= m; i++) {
		scanf("%d %d", &opt, &l);
		int tmp1 = ++tot;
		int tmp2 = ++tot;
		if(opt == 1) {
			int a, b;
			scanf("%d %d", &a, &b);
			Query(1, a, a, tmp1, 1);
			Query(1, b, b, tmp2, 0);
		}
		else if(opt == 2) {
			int a1, a2, b1;
			scanf("%d %d %d", &a1, &a2, &b1);
			Query(1, a1, a2, tmp1, 1);
			Query(1, b1, b1, tmp2, 0);
		}
		else if(opt == 3) {
			int a1, b1, b2;
			scanf("%d %d %d", &a1, &b1, &b2);
			Query(1, a1, a1, tmp1, 1);
			Query(1, b1, b2, tmp2, 0);
		}
		else {
			int a1, a2, b1, b2;
			scanf("%d %d %d %d", &a1, &a2, &b1, &b2);
			Query(1, a1, a2, tmp1, 1);
			Query(1, b1, b2, tmp2, 0);
		}
		Addedge(tmp1, tmp2, l);
	}
	t = tot + 1;
	Addedge(s, Begin, n);//连接源点到左树1的点 
	Addedge(End, t, INF);//连接右树k的点到汇点 
	printf("%d", Dinic());
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值