[HDU]5691Sitting in Line (状压DP)

题意

给定一列数,要求重新安排这列数,使得相邻两数的乘积之和最大。
有一定限制:某些位置上只能填某个数。
\(n\le16\)

题解

很明显的状压复杂度。
显然当前填的数只与上个数有关。
一开始想以阶段作为状态\(f[i][j][k]\)表示前i个位置,填的状态为k,最大的乘积。
成功地T了这道题。

然后考虑哪里有状态多余。
发现其实直接去掉第一维的枚举就行了。
因为在状压的过程中,都是用比较小的j去更新大的j(因为二进制中去掉一个1,数肯定变小)
所以枚举这个位置是不必要的。
直接对于每个j去更新就行了

#include <bits/stdc++.h>
#define int long long
#define Mid ((l + r) >> 1)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
using namespace std;
int read(){
	char c; int num, f = 1;
	while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0';
	while(c = getchar(), isdigit(c)) num = num * 10 + c - '0';
	return f * num;
}
const int inf = -1ll << 50;
int n, f[1 << 16][17], vis[17], pos[17], num[17], cnt[1 << 16];
void up(int &x, int y) {if(x < y) x = y;}
void work(int cc) {
	n = read();
	for(int i = 0; i < n; i++) vis[i] = -1;
	for(int i = 0; i < n; i++) num[i] = read(), pos[i] = read() + 1;
	for(int j = 0; j < 1 << n; j++) for(int k = 0; k <= n; k++) f[j][k] = inf;
	for(int i = 0; i < n; i++) if(!pos[i] || pos[i] == 1) f[1 << i][i] = 0;
	for(int s = 0, t; s < (1 << n); s++, t = cnt[s] + 1) {
		for(int j = 0; j < n; j++) if(f[s][j] > inf){
			for(int k = 0; k < n; k++) {
				if(j == k || s & (1 << k) || (pos[k] && pos[k] != t)) continue;
				up(f[s | (1 << k)][k], f[s][j] + num[j] * num[k]);
			}
		}
	}
	int ans = inf;
	for(int i = 0; i < n; i++) ans = max(ans, f[(1 << n) - 1][i]);
	printf("Case #%lld:\n%lld\n", cc, ans);	
}
signed main()
{
	for(int i = 1; i < 1 << 16; i++) 
		cnt[i] = cnt[i >> 1] + (i & 1);
	int Case = read(), cc = 0;
	while(Case--) work(++cc);	
	return 0;
}
/*
f[i][j]表示前j个位置,坐法为i的最大大小是多少
*/

题意

给定一列数,要求重新安排这列数,使得相邻两数的乘积之和最大。
有一定限制:某些位置上只能填某个数。
\(n\le16\)

题解

很明显的状压复杂度。
显然当前填的数只与上个数有关。
一开始想以阶段作为状态\(f[i][j][k]\)表示前i个位置,填的状态为k,最大的乘积。
成功地T了这道题。

然后考虑哪里有状态多余。
发现其实直接去掉第一维的枚举就行了。
因为在状压的过程中,都是用比较小的j去更新大的j(因为二进制中去掉一个1,数肯定变小)
所以枚举这个位置是不必要的。
直接对于每个j去更新就行了

#include <bits/stdc++.h>
#define int long long
#define Mid ((l + r) >> 1)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
using namespace std;
int read(){
	char c; int num, f = 1;
	while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0';
	while(c = getchar(), isdigit(c)) num = num * 10 + c - '0';
	return f * num;
}
const int inf = -1ll << 50;
int n, f[1 << 16][17], vis[17], pos[17], num[17], cnt[1 << 16];
void up(int &x, int y) {if(x < y) x = y;}
void work(int cc) {
	n = read();
	for(int i = 0; i < n; i++) vis[i] = -1;
	for(int i = 0; i < n; i++) num[i] = read(), pos[i] = read() + 1;
	for(int j = 0; j < 1 << n; j++) for(int k = 0; k <= n; k++) f[j][k] = inf;
	for(int i = 0; i < n; i++) if(!pos[i] || pos[i] == 1) f[1 << i][i] = 0;
	for(int s = 0, t; s < (1 << n); s++, t = cnt[s] + 1) {
		for(int j = 0; j < n; j++) if(f[s][j] > inf){
			for(int k = 0; k < n; k++) {
				if(j == k || s & (1 << k) || (pos[k] && pos[k] != t)) continue;
				up(f[s | (1 << k)][k], f[s][j] + num[j] * num[k]);
			}
		}
	}
	int ans = inf;
	for(int i = 0; i < n; i++) ans = max(ans, f[(1 << n) - 1][i]);
	printf("Case #%lld:\n%lld\n", cc, ans);	
}
signed main()
{
	for(int i = 1; i < 1 << 16; i++) 
		cnt[i] = cnt[i >> 1] + (i & 1);
	int Case = read(), cc = 0;
	while(Case--) work(++cc);	
	return 0;
}
/*
f[i][j]表示前j个位置,坐法为i的最大大小是多少
*/
posted @ 2021-02-07 09:37  _onglu  阅读(42)  评论(0编辑  收藏  举报