zoukankan      html  css  js  c++  java
  • [题解] T'ill It's Over

    前言

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

    题目链接

    题目大意

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

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

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

    思路

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

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

    使用一个类似于中转站的两个节点记为 (tmp1)(tmp2) ,将 (a) 区间的所有值连向 (tmp1) ,将 (tmp2) 连向 (b) 区间的所有值,容量为无穷大。则 (tmp1)(tmp2) 连一条容量为 (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);
    

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

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

    使用线段树,建立两颗线段树,如下图。
    在这里插入图片描述

    (s) 连向第一棵线段树的 ([1,1]) 区间的点,容量为 (n) ,第二棵线段树的 ([k,k]) 区间的点连向 (t) ,容量为极大值,和暴力差不多。

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

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

    最后跑一边最大流 Dinic ,在随机图上 Dinic 普遍优于其他 (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;
    }
    
  • 相关阅读:
    this指向详解
    领域驱动设计-1-概述
    算法 表达式求值
    进制转换
    IDEA Junit FileNotFoundException: class path resource [spring/spring.xml] cannot be opened because it does not exist
    aes加密示例
    create an oauth app
    搭建docusaurus博客
    Vue项目整体架构记要
    vue+element 获取验证码
  • 原文地址:https://www.cnblogs.com/C202202chenkelin/p/14612686.html
Copyright © 2011-2022 走看看