【ZOJ - 3963】Heap Partition (STLset,二叉树的性质,构造,贪心,思维)

版权声明:欢迎学习我的博客,希望ACM的发展越来越好~ https://blog.csdn.net/qq_41289920/article/details/89060347

题干:

A sequence S = {s1, s2, ..., sn} is called heapable if there exists a binary tree Twith n nodes such that every node is labelled with exactly one element from the sequence S, and for every non-root node si and its parent sj, sj ≤ si and j < ihold. Each element in sequence S can be used to label a node in tree T only once.

Chiaki has a sequence a1, a2, ..., an, she would like to decompose it into a minimum number of heapable subsequences.

Note that a subsequence is a sequence that can be derived from another sequence by deleting some elements without changing the order of the remaining elements.

Input

There are multiple test cases. The first line of input contains an integer T, indicating the number of test cases. For each test case:

The first line contain an integer n (1 ≤ n ≤ 105) — the length of the sequence.

The second line contains n integers a1, a2, ..., an (1 ≤ ai ≤ n).

It is guaranteed that the sum of all n does not exceed 2 × 106.

Output

For each test case, output an integer m denoting the minimum number of heapable subsequences in the first line. For the next m lines, first output an integer Ci, indicating the length of the subsequence. Then output Ci integers Pi1, Pi2, ..., PiCiin increasing order on the same line, where Pij means the index of the j-th element of the i-th subsequence in the original sequence.

Sample Input

4
4
1 2 3 4
4
2 4 3 1
4
1 1 1 1
5
3 2 1 4 1

Sample Output

1
4 1 2 3 4
2
3 1 2 3
1 4
1
4 1 2 3 4
3
2 1 4
1 2
2 3 5

Hint

题目大意:

用给出的数列a1,a2,a3....an构造二叉树,满足对于下标i和j,有i<j 且ai<=aj满足ai是aj的父节点,问最少需要构造几棵树,并输出每棵树的元素个数和每个元素对应原数组的下标。

解题报告:

从前往后在线处理,用set维护可用节点的权值val和集合编号id和可用子节点数res,每一次贪心找到小于该值的最大的那个节点,插入到其子节点并且维护set中元素(也就是这个元素的根节点的res值),如果找不到该元素,就新开一个集合记录就可以了。注意代码姿势、、、另外因为这题会有重复的val出现,所以需要multiset不能set。

AC代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define F first
#define S second
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
typedef pair<int,int> PII;
const int MAX = 2e5 + 5;
int f[MAX];
struct Node {
	int val;
	int res;
	int id;
	Node(){}
	Node(int val,int res,int id):val(val),res(res),id(id){}
	bool operator<(const Node & b) const {
		return val < b.val;
	}
} ;
multiset<Node> ss;
int main()
{
	int t,n;
	cin>>t;
	while(t--) {
		int tot = 0;
		ss.clear();
		scanf("%d",&n);
		for(int x,i = 1; i<=n; i++) {
			scanf("%d",&x);
			auto it = ss.lower_bound(Node(x,0,0));
			if((int)ss.size()==0 || (it == ss.begin() && (*it).val > x)) {				
				tot++;
				f[i] = tot;
				ss.insert(Node(x,2,f[i]));
			}
			else {
				if(it == ss.end()) --it;
				auto cur = *it;
				if(cur.val > x) --it;//找到第一个小于等于该值的。 
				cur = *it;
				ss.erase(it);
				f[i] = cur.id;
				cur.res--;
				ss.insert(Node(x,2,f[i]));
				if(cur.res > 0) ss.insert(cur);
			}
		}
		vector<int> vv[tot+1];
		for(int i = 1; i<=n; i++) {
			vv[f[i]].push_back(i);
		}
		printf("%d\n",tot);
		for(int i = 1; i<=tot; i++) {
			int up = (int)vv[i].size();
			printf("%d ",up);
			for(int j = 0; j<up; j++) {
				printf("%d%c",vv[i][j],j == up-1 ? '\n' :' ');
			}
			
		}
	}
	return 0 ;
}

WA的部分:(这样写就WA了,,因为你没有更新set中的值,只是在外面更改了cur)

			else {
				if(it == ss.end()) --it;
				auto cur = *it;
				if(cur.val > x) --it;//找到第一个小于等于该值的。 
				cur = *it;
				f[i] = cur.id;
				cur.res--;
				ss.insert(Node(x,2,f[i]));
				if(cur.res == 0) ss.erase(it);
			}

AC代码2:(set维护下标)

思路就是先从大到小排序,这样就不需要考虑值的大小问题了,这样只需要考虑下标要求的一个小于号,这一点我们可以用set来维护。想法是这样的,相当于我们每次都插入这一个根结点,然后看set里面能否有节点可以当他的孩子节点,注意因为值是由大到小排好序的,所以set里面理论上拿哪一个值都不影响结果的正确性,但是因为这题还需要下标的关系,所以我们还需要二分查找一下合法的下标(因为我们能用的比我们大的值,要求下标也比我大,所以我需要lower或者upper查询(其实这里都一样,因为位置都是unique的),得到我可以用来当孩子结点的下标(一共可以选两个当孩子,选哪个都无所谓,只需要下标满足就可以了),如果实在没有可以当下标的(it == ss.end()),那就只能break掉了,相当于只选择一个当孩子节点。),并将该下标所属的集合改成当前遍历的那个数为根节点的集合。一次类推,最后并查集输出答案就行了。其实还是相当于一个逆向思维,从叶子结点往上构造。但是较AC代码1来说,这个比较难想。

相应的set中从大到小排序,pair从小到大排序,应该也可以做。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define F first
#define S second
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
typedef pair<int,int> PII;
const int MAX = 2e5 + 5;
set<int> ss;
PII p[MAX];
int f[MAX],tot,n;
int main() {
	int t;
	cin>>t;
	while(t--) {
		scanf("%d",&n);
		ss.clear();tot=0;
		for(int i = 1; i<=n; i++) f[i] = i;
		for(int x,i = 1; i<=n; i++) {
			scanf("%d",&x);p[i] = pm(x,i);
		}
		sort(p+1,p+n+1,greater<PII>());
		for(int i = 1; i<=n; i++) {
			int pos = p[i].S;
			for(int j = 0; j<2; j++) {
				auto it = ss.lower_bound(pos);
				if(it == ss.end()) break;
				f[*it] = pos;
				ss.erase(it);
			}
			ss.insert(pos);
		}
		vector<int> vv[n+1];
		for(int i = 1; i<=n; i++) {
			if(f[i] != i) f[i] = f[f[i]];//这里因为每一个f[i]一定是前面的值,并且一定已经是更新好的,所以不需要并查集了,只需要一步压缩就可以了。
			vv[f[i]].push_back(i);
		}
		for(int i = 1; i<=n; i++) {
			if(vv[i].size()) tot++;
		}
		printf("%d\n",tot);
		for(int i = 1; i<=n; i++) {
			if(vv[i].empty()) continue;
			int up = (int)vv[i].size();
			printf("%d ",up);
			for(int j = 0; j<up; j++) {
				printf("%d%c",vv[i][j],j == up-1 ? '\n' :' ');
			}
		}
	}
	return 0 ;
}

猜你喜欢

转载自blog.csdn.net/qq_41289920/article/details/89060347
ZOJ