树形DP+并查集+左偏树, HDU-5575,Discover Water Tank,2015上海现场赛D题

只是ACM/IICPC 2015 上海区域赛的一道题。原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=5575

题目描述

N-1个木板把一个水箱划分成了N部分(从左到右形成了编号为[1,N]的N个小水箱)。这些木板高度不尽相同。因为水往低处流和木板相隔,所以整个大水箱中有若干个高低不同的水平面。

接下来进行M次探测,探测指定地方指定高度有没有水。这些探测中包含错误结果,求 可能的正确探测数目的最大值

输入

T组数据。
每组数据以N和M开始。N-1为木板个数,M代表探测次数。
接下来一行是N-1个整数,表示每块木板的高度。
然后是M行,(x,y,z)三个数表示在编号为x的水箱中测试y+0.5米高的地方是否有水,z=0表示没有水, z=1表示有水。

输出

可能的正确探测数目的最大值。

Sample Input

2
3 4
3 4
1 3 1
2 1 0
2 2 0
3 3 1
2 2
2
1 2 0
1 2 1

Sample Output

Case #1: 3
Case #2: 1

提示

对于sample input中的第一组数据,认为第一次探测错误,那么可能的正确探测数目的最大值就就是3。


ac代码

//HDOJ_5575_AC
#include 
#include 
#include 
#include 

using namespace std;

//多组测试case时,可以复用数组,不需动态申请用完再回收。
const int MAX_N = 1e5 + 1;
const int MAX_M = MAX_N * 2;
const int INF = 1e10 + 1;
typedef pair HeightAndTankNo;
struct LeftistTreeNode;

int caseNum = 0, caseNo = 0, answer = 0;
int n, detectNum;
int leftHeightOfTank[MAX_N], rightHeightOfTank[MAX_N];
//第i个水箱的相邻水箱编号, 水箱合并时会用
int LeftTank[MAX_N],RightTank[MAX_N];
//有水探测,无水探测
int hasWaterDetect[MAX_N], noWaterDetect[MAX_N];
//有水探测对应的数组
vector hasWaterDetectVector;

//leftistTree[i] 表示 第i个水箱对应的左偏树节点编号
int leftistTree[MAX_N];

int unionFindSet[MAX_N];
//左偏树节点编号,通过自增来用
int lefttistNodeNo;

//node[i] means i-th node
LeftistTreeNode *nodes;
struct LeftistTreeNode {
	int value;
	int dist;
	int lchild, rchild;

	LeftistTreeNode() {
		value = lchild = rchild = 0;
		dist = 1;
	}
	static void init() {
		if (nodes == NULL)
			nodes = new LeftistTreeNode[MAX_M];
		for (int i = 0; i < MAX_M; i++) {
			nodes[i].value = nodes[i].lchild = nodes[i].rchild = 0;
			nodes[i].dist = 1;
		}
	}
	/**
	 *
	 * @param a tree
	 * @param b tree
	 * @return new root
	 */
	static int merge(int a, int b) {
		if (a == 0)
			return b;
		if (b == 0)
			return a;
		//小顶堆
		if (nodes[a].value > nodes[b].value)
			swap(a, b);
		nodes[a].rchild = merge(nodes[a].rchild, b);
		if (nodes[nodes[a].lchild].dist < nodes[nodes[a].rchild].dist)
			swap(nodes[a].lchild, nodes[a].rchild);
		nodes[a].dist = nodes[nodes[a].rchild].dist + 1;
		return a;
	}
	/**
	 * @param a tree
	 * @param b new node
	 * @return new root
	 */
	static int insert(int a, int b) {
		return merge(a, b);
	}
	/**
	 * delete the top element and return new root
	 */
	static int pop(int a) {
		return merge(nodes[a].lchild, nodes[a].rchild);
	}
};

//并查集
struct UnionFindSet {
	static void init() {
		for (int i = 0; i < MAX_N; i++)
			unionFindSet[i] = i;
	}
	static int findParent(int x) {
		return unionFindSet[x] == x ?
				x : unionFindSet[x] = findParent(unionFindSet[x]);
	}
	static void merge(int x, int y) {
		unionFindSet[y] = unionFindSet[x];
	}
};

struct Main {

	void read() {
		cin >> n >> detectNum;
		//第i个木板的高度, 也就是第i个水箱的右挡板的高度
		for (int i = 1; i < n; i++)
			cin >> rightHeightOfTank[i];
	}
	void init() {
		answer = 0;
		UnionFindSet::init();
		LeftistTreeNode::init();
		hasWaterDetectVector.clear();

		leftHeightOfTank[1] = rightHeightOfTank[n] = INF;
		LeftTank[n] = n - 1;
		lefttistNodeNo = 0;

		for (int i = 1; i < n; i++) {
			leftHeightOfTank[i + 1] = rightHeightOfTank[i];
			LeftTank[i] = i - 1;
			RightTank[i] = i + 1;
		}
		for (int i = 1; i <= n; i++) {
			hasWaterDetect[i] = noWaterDetect[i] = 0;
			leftistTree[i] = 0;
		}
	}
	void calc() {
		while (detectNum--) {
			int x, y, z;
			//(x,y,z)表示	在编号为x的水箱中测试y+0.5 米高的地方是否有水,z=0表示没有水, z=1表示有水。
			cin >> x >> y >> z;
			//有水,放数组
			if (z)
				hasWaterDetectVector.push_back(HeightAndTankNo(y + 0.5, x));
			//没水
			else {
				//每次无水探测, 答案+1
				answer++;

				//每次无水探测对应一棵 左偏树
				nodes[++lefttistNodeNo].value = y;

				//x号水箱无水探测的最低位置
				leftistTree[x] =
						leftistTree[x] != 0 ?
								LeftistTreeNode::merge(leftistTree[x],
										lefttistNodeNo) :
								lefttistNodeNo;
			}
		} //while (detectNum--)
		sort(hasWaterDetectVector.begin(), hasWaterDetectVector.end());
		for (unsigned int i = 0; i < hasWaterDetectVector.size(); i++) {
			int tankNo = UnionFindSet::findParent(
					hasWaterDetectVector[i].second);
			//探测到有水的高度
			float height = hasWaterDetectVector[i].first;
			//水位向左溢出
			while (height > leftHeightOfTank[tankNo]) {
				mergeTank(tankNo, LeftTank[tankNo]);
				tankNo = UnionFindSet::findParent(tankNo);
			}
			//水位向右溢出
			while (height > rightHeightOfTank[tankNo]) {
				mergeTank(tankNo, RightTank[tankNo]);
				tankNo = UnionFindSet::findParent(tankNo);
			}
			while (leftistTree[tankNo] != 0
					&& nodes[leftistTree[tankNo]].value < height) {
				leftistTree[tankNo] = LeftistTreeNode::pop(leftistTree[tankNo]);
				noWaterDetect[tankNo]++;
			}

			if (++hasWaterDetect[tankNo] >= noWaterDetect[tankNo]) {
				answer += (hasWaterDetect[tankNo] - noWaterDetect[tankNo]);
				hasWaterDetect[tankNo] = noWaterDetect[tankNo] = 0;
			}
		}
	}
	void output() {
		cout << "Case #" << ++caseNo << ": " << answer << endl;
	}
	void mergeTank(int x, int y) {
		x = UnionFindSet::findParent(x);
		y = UnionFindSet::findParent(y);
		if (x == y)
			return;
		UnionFindSet::merge(x, y);
		//x在左,y在右
		if (x < y) {
			rightHeightOfTank[x] = rightHeightOfTank[y];
			LeftTank[RightTank[x]] = x;
			RightTank[x] = RightTank[y];
		} else {
			leftHeightOfTank[x] = leftHeightOfTank[y];
			RightTank[LeftTank[x]] = x;
			LeftTank[x] = LeftTank[y];
		}
		leftistTree[x] = LeftistTreeNode::merge(leftistTree[x], leftistTree[y]);
		noWaterDetect[x] += noWaterDetect[y];
		hasWaterDetect[x] += hasWaterDetect[y];
	}

	void entry() {
		cin >> caseNum;
		while (caseNum--) {
			read();
			init();
			calc();
			output();
		}
	}

};

int main() {
	std::ios::sync_with_stdio(false);
//HDOJ系统在运行时会注入ONLINE_JUDGE这个宏定义
#ifndef ONLINE_JUDGE
	freopen("d:\\code-practice\\in.txt", "r", stdin);
#endif
	Main().entry();
	return 0;
}

你可能感兴趣的:(动态规划)