1. 程式人生 > 其它 >[2021.8集訓Day1/JZOJ.4743]【NOIP2016提高A組模擬9.2】積木

[2021.8集訓Day1/JZOJ.4743]【NOIP2016提高A組模擬9.2】積木

目錄

[2021.8集訓Day1/JZOJ.4743]【NOIP2016提高A組模擬9.2】積木

題目

思路

搜尋+剪枝

可以考慮把一個積木拆成3個,為了方便,用以下結構體儲存

struct BLOCK {
	int a , b , h , id;//id配合vis防止同一個積木用多次
	void push(int a_ , int b_ , int h_ , int id_) {
		id = id_ , h = h_;
		if(a_ < b_)
			a = a_ , b = b_;
		else
			a = b_ , b = a_;
	}
} block[N];

//in main
	for(int i = 1 ; i <= n ; i++) {
		int a = read() , b = read() , c = read();
		block[++m].push(a , b , c , i);
		block[++m].push(b , c , a , i);
		block[++m].push(c , a , b , i);
	}

為了讓前幾次搜尋的結果盡接近高度最大值,我們將積木排序:

typedef long long lint ;
typedef unsigned long long ulint ;
bool cmp(BLOCK a , BLOCK b) {
	lint s1 = (lint)a.a * a.b , s2 = (lint)b.a * b.b;
	return s1 == s2 ? a.h > b.h : s1 > s2;
}
//in main
	std::sort(block + 1 , block + m + 1 , cmp);

另一個最優性剪枝:

當前用過的木塊的集合為\(sta\)

,頂層短邊長為\(a\),長邊為\(b\),將\((sta,a,b)\)​作為一個狀態,並用Hash記錄這個狀態的高度最大值,如果以後搜到一樣的狀態且高度更小,則直接剪枝.

這樣的做法不用火車頭的情況下在JMOJ的老爺機會T,在洛谷(線上IDE)則可以在0.7~0.9s內出結果

程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define reg register

int read() {
	int re = 0;
	char c = getchar();
	bool negt = false;
	while(c < '0' || c > '9')
		negt |= (c == '-') , c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0' , c = getchar();
	return negt ? -re : re;
}

typedef long long lint ;
typedef unsigned long long ulint ;

const int N = 20 * 3;

struct BLOCK {
	int a , b , h , id;
	void push(int a_ , int b_ , int h_ , int id_) {
		id = id_ , h = h_;
		if(a_ < b_)
			a = a_ , b = b_;
		else
			a = b_ , b = a_;
	}
} block[N];
bool cmp(BLOCK a , BLOCK b) {
	lint s1 = (lint)a.a * a.b , s2 = (lint)b.a * b.b;
	return s1 == s2 ? a.h > b.h : s1 > s2;
}

#define CHAIN_HASH 0//雜湊切換開關
#if CHAIN_HASH
class Hashing {//安全雜湊,極低概率衝突,但是慢
	private :
		static const int mod = 10000003;
		static const ulint mul = 100000000;
		int cnt;
		struct CHAIN {
			ulint key;
			int nxt;
			int h;
		} chain[10000000];
		int head[mod + 10];

		inline ulint hash(ulint sta , ulint a , ulint b) {
			return ((a + 1) * mul + b) * (1ull << 15) + sta;
		}
		int high[mod + 10];
	public :
		inline bool change(ulint sta , int a , int b , int h) {
			ulint key = hash(sta , a , b);
			for(reg int i = head[key % mod] ; i ; i = chain[i].nxt)
				if(chain[i].key == key) {
					if(chain[i].h < h) {
						chain[i].h = h;
						return true;
					} else
						return false;
				}
			++cnt;
			chain[cnt].h = h , chain[cnt].key = key , chain[cnt].nxt = head[key % mod] , head[key % mod] = cnt;
			return true;
		}
} hash;
#else
class Hashing {
	private :
		static const int mod = 10000003;
    //如果用10000007,在第10組資料會出現衝突,另外,mod不能開太大,不然下面的陣列跟著變大,速度會變慢0.2~0.3s
		static const ulint mul = 100000000;
		inline ulint hash(ulint sta , ulint a , ulint b) {
			return ((a + 1) * mul + b) % mod * (1ull << 15) % mod + sta;
		}
		int high[mod + 10];
	public :
		inline int GetHigh(ulint sta , int a , int b) {
			return high[hash(sta , a , b) % mod];
		}
		inline void change(ulint sta , int a , int b , int h) {
			high[hash(sta , a , b) % mod] = h;
		}
} hash;
#endif

int n , m;
int ans;
bool vis[N];

void dfs(ulint sta , int a , int b , int h) {
    //剪枝
#if CHAIN_HASH
	if(!hash.change(sta , a , b , h))
		return ;
#else
	int hh = hash.GetHigh(sta , a , b);
	if(hh > h)
		return;
	else
		hash.change(sta , a , b , h);
#endif

	if(h > ans)
		ans = h;
	for(int i = 1 ; i <= m ; i++)
		if(!vis[block[i].id] && block[i].a <= a && block[i].b <= b) {
			vis[block[i].id] = true;
			dfs(sta + (1ull << i) , block[i].a , block[i].b , h + block[i].h);
			vis[block[i].id] = false;
		}
}
int main() {
//	std::cout << sizeof(hash);//MLE檢查
	n = read();
	for(int i = 1 ; i <= n ; i++) {
		int a = read() , b = read() , c = read();
		block[++m].push(a , b , c , i);
		block[++m].push(b , c , a , i);
		block[++m].push(c , a , b , i);
	}
	std::sort(block + 1 , block + m + 1 , cmp);

	dfs(0 , 100000000 , 100000000 , 0);
	std::cout << ans;
	return 0;
}
/*
3
100 100 100
90 88 12
15 15 89
*/

狀壓DP

資料範圍比較小,可以考慮狀壓DP

\(f_{k,i,j}\)表示當前用過的積木的集合為\(k\),位於頂層的積木是第\(i\)個,頂層積木的狀態是\(j\in\{0,1,2\}\)(三個不同的面分別作為底面).

\(k\)在二進位制下\(1\)​的數量從小到大列舉即可(可用BFS實現).時間複雜度\(O(2^n\cdot (3n)^2)\)

程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define reg register

using std::cout;
int read() {
	int re = 0;
	char c = getchar();
	bool negt = false;
	while(c < '0' || c > '9')
		negt |= (c == '-') , c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0' , c = getchar();
	return negt ? -re : re;
}

const int N = 15;

struct BLOCK {
	int a , b , h , id;
	void push(int a_ , int b_ , int h_ , int id_) {
		id = id_ , h = h_;
		if(a_ < b_)
			a = a_ , b = b_;
		else
			a = b_ , b = a_;
	}
} block[N * 3];


int n;
int f[1 << N + 1][N][3];

bool vis[1 << N + 1];
int max(int a , int b) {
	return a > b ? a : b;
}

void output2(int x , int res = n) {
	if(res == 0)
		return ;
	output2(x >> 1 , res - 1);
	putchar((x & 1) + '0');
}
int main() {
	n = read();
	for(int i = 0 ; i < n ; i++) {
		int a = read() , b = read() , c = read();
		block[i + n * 0].push(a , b , c , i);
		block[i + n * 1].push(b , c , a , i);
		block[i + n * 2].push(c , a , b , i);
	}
	
	std::queue <int> q;
	
	for(int i = 0 ; i < n ; i++) {
		q.push(1 << i);
		for(int j = 0 ; j <= 2 ; j++)
			f[1 << i][i][j] = block[i + n * j].h;
	}
	while(!q.empty()) {
		int k = q.front();
		
		q.pop();
		for(int i = 0 ; i < n ; i++)
			for(int j = 0 ; j < n ; j++)
				if((k >> i & 1) == 0 && (k >> j & 1) == 1) {
					int k1 = (k | (1 << i));
					for(int x = 0 ; x <= 2 ; x++)
						for(int y = 0 ; y <= 2 ; y++)
							if(block[i + n * x].a <= block[j + n * y].a && block[i + n * x].b <= block[j + n * y].b) {
								f[k1][i][x] = max(f[k1][i][x] , f[k][j][y] + block[i + n * x].h);
								if(!vis[k1])
									vis[k1] = true , q.push(k1);
							}
				}
	}
	
	int ans = 0;
	for(int i = 0 ; i < (1 << n) ; i++)
		for(int j = 0 ; j < n ; j++)
			for(int k = 0 ; k <= 2 ; k++)
				ans = max(ans , f[i][j][k]);
	std::cout << ans;
	return 0;
}
/*
3
100 100 100
90 88 12
15 15 89
*/

資料(#3,5,10)

#3

3
100 100 100
90 88 12
15 15 89

#5

15
120 110 20
120 10 10
110 120 90
30 100 120
70 30 70
10 40 70
120 120 40
100 20 70
100 90 90
100 80 50
20 30 80
100 80 100
10 100 30
80 10 90
60 100 50

#10

15
391494 11974 2068
673 369415 237
1000415 16 16
586590 77621 1291241
390595 16 16
16 16 122297
16 16 90154
1591281 1292122 1323875
1522 853 564603
16 752587 16
775404 253 128
37350 321131 199138
409134 16 22
18 52 560551
897982 729 802