【ZOJ3951 The 17th Zhejiang University Programming Contest D】【树形DP 合并思想 复杂度计算】Independent Set 构造树使得不同

Independent Set

Time Limit: 1 Second       Memory Limit: 65536 KB       Special Judge

Chiaki has a positive integer m and she would like to construct a tree with at most 15 vertices in such a manner that the number of distinct nonempty independent sets is exactly m.

Note that an independent set is a subset of vertices of a graph such that every two distinct vertices are not adjacent.

Input

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

The first line contains an integer m (1 ≤ m ≤ 2000).

Output

For each test case, if there is no such graph, output -1 on a single line. Otherwise, output an integer n (1 ≤ n ≤ 15) denoting the number of vertices. Then in each of the next n - 1 lines, output two integers x and y (1 ≤ xy ≤ nx ≠ y) denoting an edge in the tree.

Sample Input
5
1
2
3
10
20
Sample Output
1
2
2 1
-1
-1
6
2 1
3 1
4 3
5 4
6 5

Author:  LIN, Xi

Source: The 17th Zhejiang University Programming Contest Sponsored by TuSimple


#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x, y) memset(x, y, sizeof(x))
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; }
const int N = 2020, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f;
template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; }
int casenum, casei;
int n, m;
int f[N][N];
struct PRE
{
	short i, j, x, y;
}pre[N][N];
void init()
{
	int cnt = 0;
	MS(f, 63); f[1][1] = 1;
	for (int i = 1; i <= 2000; ++i)
	{
		for (int j = 1; i + j <= 2000; ++j)if(f[i][j] <= 15)
		{
			//f[i * y][j * (x + y)]
			for (int y = 1; i * y <= 2000; ++y)
			{
				for (int x = 1; j * (x + y) <= 2000; ++x)if (f[i][j] + f[x][y] <= 15)
				{
					++cnt;
					int tmp = f[i][j] + f[x][y];
					if (tmp < f[i * y][j * (x + y)])
					{
						f[i * y][j * (x + y)] = tmp;
						pre[i * y][j * (x + y)] = { (short)i, (short)j, (short)x, (short)y };
					}
				}
			}
			//f[x * j][y * (i + j)]
			for (int x = 1; x * j <= 2000; ++x)
			{
				for (int y = 1; y * (i + j) <= 2000; ++y)if (f[i][j] + f[x][y] <= 15)
				{
					++cnt;
					int tmp = f[i][j] + f[x][y];
					if (tmp < f[x * j][y * (i + j)])
					{
						f[x * j][y * (i + j)] = tmp;
						pre[x * j][y * (i + j)] = { (short)x, (short)y, (short)i, (short)j };
					}
				}
			}
		}
	}
	//printf("Compute Time = %d\n", cnt);
}
int ID;
void print(int rt, int x, int y)
{
	if (x == 1 && y == 1)return;
	print(rt, pre[x][y].i, pre[x][y].j);
	printf("%d %d\n", rt, ++ID);
	print(ID, pre[x][y].x, pre[x][y].y);
}
int main()
{
	init();
	scanf("%d", &casenum);
	for (casei = 1; casei <= casenum; ++casei)
	{
		scanf("%d", &m); ++m;
		ID = 1;
		bool flag = 0;
		for (int i = 1; i < m; ++i)if (f[i][m - i] <= 15)
		{
			printf("%d\n", f[i][m - i]);
			print(1, i, m - i);
			flag = 1;
			break;
		}
		if (!flag)puts("-1");
	}
	return 0;
}
/*
【trick&&吐槽】
1,csy还是套路深啊
2,状态太复杂的还是用下标表示得好
3,我们四重循环,实际复杂度却并不高,但是要用对称DP来实现无缝的值域覆盖

【题意】
我们最多使用15个节点,构造出一棵树,使得——这棵树中恰好有m个不同的集合{}
对于其中任意一个集合,其必须要是个独立集,即:其里面的任意两个点之间没有直接边相连。

【分析】
我们假设自己已经知道了树形态,思考如何求出独立集。
这个可以是2 ^ n * n枚举,当然最好的方法是树形DP。
这是一棵树,所以独立集的判定其实较为简单一点。
同时,独立集的转移也比较简单,我们只要考虑一个点取与不取两种情况。

于是,我们可以定义状态——
f[i][j]表示,如果我们选取根节点,会有i个独立集;如果我们不选取根节点,会有j个独立集,在这种条件下对应的最少节点数。

为什么定义出这么一个状态来呢?
因为,我们考虑独立集的计数时,是要求没有相邻点的。
于是,需要知道一个点选或不选对应的独立集个数。而这个点其实对应于一个根。
即:我们每次操作的时候必然基于一个点,就不妨把这个点设置为根。
我们知道了f[i][j]与f[x][y]这样两个树形态,我们考虑合并。
当然合并的对象是两个子树的根,合并之后的根可能是f[i][j]的根,也可能是f[x][y]的根。

假如合并之后的根是f[i][j]的根,与f[x][y]做合并,变成了f[i * y][j * (x + y)];
假如合并之后的根是f[x][y]的根,与f[i][j]做合并,变成了f[x * j][y * (i + j)];

初始状态是什么呢?f[1][1] = 1. 即我们把0个点选取的独立集也考虑,这样才使得合并计数准确。至于输入的m,使得m += 1即可。

【时间复杂度&&优化】
Compute Time = 206561

*/


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值