[BZOJ]2734 [HNOI2012] 集合選數 狀壓DP 思路神題

2734: [HNOI2012]集合選數

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 1475  Solved: 876
[Submit][Status][Discuss]

Description

《集合論與圖論》這門課程有一道作業題,要求同學們求出{1, 2, 3, 4, 5}的所有滿足以 下條件的子集:若 x 在該子集中,則 2x 和 3x 不能在該子集中。同學們不喜歡這種具有枚舉性 質的題目,於是把它變成了以下問題:對於任意一個正整數 n≤100000,如何求出{1, 2,..., n} 的滿足上述約束條件的子集的個數(只需輸出對 1,000,000,001 取模的結果),現在這個問題就 交給你了。 
 

Input

 只有一行,其中有一個正整數 n,30%的數據滿足 n≤20。 
 

Output


 僅包含一個正整數,表示{1, 2,..., n}有多少個滿足上述約束條件 的子集。 
 

Sample Input


4

Sample Output

8

【樣例解釋】

有8 個集合滿足要求,分別是空集,{1},{1,4},{2},{2,3},{3},{3,4},{4}。

HINT

Source

[Submit][Status][Discuss]


HOME Back

  一開始有一個樸素的思路就是dp[i][0/1]表示當前選還是不選的方案數, 然後減去dp[i/2][1]和dp[i/3][1]的, 發現可能會刪重就需要記錄一些其他的東西.... 發現處理不好gg. 於是往根號方面的複雜度去想也沒有什麼卵用. 看了一下30%的數據感覺暴力狀壓可過. 然後實在想不出來, orz了題解.

  解題思路真的有點神... 總結一下思路. 我們可以看到題目中只有2x, 3x這樣的限制可以產生一點疑惑:爲什麼不能再是3x, 4x... 而是隻有兩個限制? 原來我認爲無非就是一種可能:如果是dp的話dp狀態會減少. 但是做了一下發現很難去除重複的情況, 選擇gg. 那還有什麼可能呢? 實際上在某些序列上的數據結構問題我們會發現可以將限制問題轉化爲幾維的限制, 比如BZOJ3489, 可以化成三維限制關係kd-tree水過(雖然我寫的樹套樹...). 那麼這道題同理, 只給出了兩位限制我們就往兩維想. 把2的倍數設爲一維, 3的倍數設爲一維. 那麼比如說我們可以得到這麼一個矩陣(二維平面).

1 3 9 27 81....

2 6 18 54 162...

4 12 36 108 324...

  橫着3倍關係, 豎着2倍關係. 我們會發現題目中要求的限制就是不能選相鄰的數!很容易發現矩陣橫(列)不超過11, 豎(行)不超過17, 那麼一個很naive的狀壓就完成了. 我們同時會發現這個矩陣沒有5, 7... 5的倍數, 7的倍數,沒關係我們可以再構建出形如這樣的矩陣:

5 15 45...

10 30 90...
20 60 120...

  這樣每個矩陣狀壓得出的答案相乘就是結果. 這相當於從不同的矩陣裏選數, 因爲每個矩陣數字不重複, 顯然可以運用乘法原理. 關於時間複雜度我也不會證明... 可以感性的想雖然有多個矩陣會不可過, 但實際上我們知道不是2的倍數又不是3的倍數就會被作爲一個矩陣的左上角. 這樣的數雖然也不少但是當數變大的時候這個矩陣也會迅速的變小(因爲橫着乘2次冪, 豎着乘3次冪), 狀壓狀態數會迅速變的非常少, 所以說感覺可過QAQ... 測了極限數據快的飛起. 這樣的複雜度如果較難分析實際上可以通過打表算來看是不是可過.

  所以總而言之, dp中題目限制屈指可數的話(因爲越高維空間時間都開銷inf啊), 除了往狀態數, 枚舉兩方面想, 實際上還可以往維度方面想, 構造圖形來dp. 這道題如果是2x,3x, 5x不可以的話, 按照之前的想法不難分析這就會是一個立方體.

#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 1;
int n, tmp, ans;
bool mark[1 << 18];
int pw[19], line[19], f[19][1 << 13], a[19][13];
inline int calc(const int &x) {
	a[1][1] = x;
	register int i, j;
	for (i = 1; ; ++ i) {
		if (i ^ 1) {
			a[i][1] = a[i - 1][1] << 1;//
			if (a[i][1] > n) {
				tmp = i - 1;
				break;
			}
		}
		mark[a[i][1]] = true;
		for (j = 2; ; ++ j) {
			a[i][j] = (a[i][j - 1] << 1) + a[i][j - 1];
			if (a[i][j] > n) {
				line[i] = j - 1;
				break;
			}
			mark[a[i][j]] = 1;
		}
	}
	for (i = 0; i <= tmp; ++ i)
		for (j = 0; j < pw[line[i]]; ++ j)
			f[i][j] = 0;
	line[0] = 1, line[tmp + 1] = 0;
	f[0][0] = 1, f[tmp + 1][0] = 0;
	for (i = 0; i <= tmp; ++ i)
		for (j = 0; j < pw[line[i]]; ++ j)
			if (f[i][j] && !(j & (j >> 1)))
				for (int k = 0; k < pw[line[i + 1]]; ++ k)
					if (!(j & k)) f[i + 1][k] = (f[i + 1][k] + f[i][j]) % mod;
	return f[tmp + 1][0];
}
int main() {
	register int i;
	ans = pw[0] = 1;
	scanf("%d", &n);
	for (i = 1; i < 19; ++ i)
		pw[i] = pw[i - 1] << 1;
	for (i = 1; i <= n; ++ i)
		if (!mark[i]) ans = 1ll * ans * calc(i) % mod;
	printf("%d\n", ans);
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章