题意:度度熊是他同时代中最伟大的数学家,一切数字都要听命于他。现在,又到了度度熊和他的数字仆人们玩排排坐游戏的时候了。游戏的规则十分简单,参与游戏的N个整数将会做成一排,他们将通过不断交换自己的位置,最终达到所有相邻两数乘积的和最大的目的,参与游戏的数字有整数也有负数。度度熊为了在他的数字仆人面前展现他的权威,他规定某些数字只能在坐固定的位置上,没有被度度熊限制的数字则可以自由地交换位置
输出数字重新排列后最大的所有相邻两数乘积的和,即max{a1⋅a2+a2⋅a3+......+aN−1⋅aN}
题解:一道状压DP的题目。其实关于状压DP的题目很久之前便有所接触,只是一直没去学,今天通过此题算是初步学习了状压DP。
状压DP本质上来说还是DP,重点在于找出每一步的状态,并且建立起走到下一步的状态转移方程。不过有的时候,状态的表示会要利用到状态压缩的方法。
状态转移方程:当第k个位置已经被固定填什么数时,向状态st里添加i并以i为结尾,dp[st|(1<<q[k])][q[k]] = max(dp[st|(1<<q[k])][q[k]], dp[st][j]+a[j]*a[q[k]])。当第k位置不固定,枚举各个数,该数不在集合时,便添加到集合里,dp[st|(1<<i)][i] = max(dp[st|(1<<i)][i], dp[st][j]+a[j]*a[i])。
代码如下:
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#define ll long long
using namespace std;
const int maxn=1<<17;
const ll inf=1e14;
int n,num[20],fix[20],pos[20];
ll dp[maxn][20];
bool ok(int p,int s){
while(s){
s=s&(s-1);
p--;
}
if(p) return false;
return true;
}
int main(){
int T;
scanf("%d",&T);
int Case=1;
while(T--){
scanf("%d",&n);
int a,p;
memset(fix,0,sizeof(fix));
memset(pos,0,sizeof(pos));
for(int i=0;i<n;++i){
scanf("%d%d",&a,&p);
num[i+1]=a;
if(++p) pos[p]=i+1,fix[i+1]=1;
}
for(int i=0;i<=(1<<(n));++i)
for(int j=0;j<=n;++j)
dp[i][j]=-inf;
if(pos[1]) dp[1<<(pos[1]-1)][pos[1]]=0;
else {
for(int i=1;i<=n;++i)
if(fix[i]==0)
dp[1<<(i-1)][i]=0;
}
for(int p=2;p<=n;++p){
for(int s=0;s<(1<<n);++s){
if(ok(p-1,s)){
if(pos[p]){
for(int l=1;l<=n;++l){
if(dp[s|(1<<(pos[p]-1))][pos[p]] < dp[s][l]+num[l]*num[pos[p]]&& dp[s][l]!=-inf){
dp[s|(1<<(pos[p]-1))][pos[p]]=dp[s][l]+num[l]*num[pos[p]];
}
}
}
else{
for(int l1=1;l1<=n;++l1){
if(fix[l1]==0&&(s&(1<<(l1-1)))==0){
for(int l2=1;l2<=n;++l2){
if(dp[s|(1<<(l1-1))][l1] < dp[s][l2]+num[l2]*num[l1]&& dp[s][l2]!=-inf)
dp[s|(1<<(l1-1))][l1] = dp[s][l2]+num[l2]*num[l1];
}
}
}
}
}
}
}
ll ans=-inf;
for(int i=1;i<=n;++i)
if(dp[(1<<n)-1][i]>ans)
ans=dp[(1<<n)-1][i];
printf("Case #%d:\n",Case++);
if(n==1) printf("%d\n",a);
else printf("%I64d\n",ans);
}
return 0;
}
等我费劲心脾写完上述代码,百度了下别人题解,便发现了自己年轻。由于没有很好掌握状态转移,导致以上代码很渣,下面贴一份别人的正解代码。
正解代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 20;
const ll INF = 1LL<<50;
ll dp[1<<N][N];
int p[N], q[N], a[N];
int main() {
int T, n;
scanf("%d", &T);
for(int kase = 1; kase <= T; kase++) {
scanf("%d", &n);
memset(q, -1, sizeof(q));
for(int i = 0; i < n; i++) {
scanf("%d%d", &a[i], &p[i]);
if(p[i] >= 0) q[p[i]] = i;
}
for(int i = 0; i < (1<<n); i++)
for(int j = 0; j < n; j++)
dp[i][j] = -INF;
if(~q[0]) dp[1<<q[0]][q[0]] = 0;
else {
for(int i = 0; i < n; i++)
dp[1<<i][i] = 0;
}
for(int st = 1; st < (1<<n); st++) {
int k = 0;
for(int i = 0; i < n; i++)
k += st>>i&1;
if(~q[k]) {
if(st & (1<<q[k])) continue;
for(int j = 0; j < n; j++) {
if(~st&(1<<j)) continue;
dp[st|(1<<q[k])][q[k]] = max(dp[st|(1<<q[k])][q[k]], dp[st][j]+a[j]*a[q[k]]);
}
}
else {
for(int i = 0; i < n; i++) {
if(st & (1<<i)) continue;
for(int j = 0; j < n; j++) {
if(~st&(1<<j)) continue;
dp[st|(1<<i)][i] = max(dp[st|(1<<i)][i], dp[st][j]+a[j]*a[i]);
}
}
}
}
ll ans = -INF;
for(int i = 0; i < n; i++)
ans = max(ans, dp[(1<<n)-1][i]);
printf("Case #%d:\n%I64d\n", kase, ans);
}
return 0;
}