题意:
有n(n<=16)个数字需要排列,其中一些的数字位置是给定的,问最后排列的ans值最大是多少。ans的定义是所有连续两个数字的乘积的和。
思路:
完全排列复杂度n!,不可取。ans乘积的和具有最优子结构。按照tsp类似的方法设定状态dp[i][j]表示状态为i的情况下,最后选择第j个数可以得到的最优解。i状态的二进制位中为1的对应的数字排列在序列最前面。
其实这个状态信息量不小,每一个状态唯一不可知的是数字排列的顺序,但这可以在状态转移的过程中获得。状态转移方程是:dp[i|(1<<k)][k] = max(dp[i|(1<<k)][k],dp[i][j]+s[j]*s[k]);
ans = max(dp[(1<<n)-1][k])(0<k<n)
,状态dp[s][j]
也可以看作是对于dp[i][j][s]
的化简,其中i表示前i个位置的数,因为i可以由s二进制位中1的个数得出,还可以知道的是状态dp[s][j]
一定向着状态dp[t][k]
转移(t > s), 因此for循环的顺序可以确定了,即从小到大。注意:此题中非法状态较多,dp过程中要注意删除,且答案可能是负数,初始值的设定要小心。
代码:
#include <bits/stdc++.h>
using namespace std;
int s[20];
int p[20];
int dp[1<<16][20];
inline int counts(int st)
{
int cn = 0;
while(st>0)
{
if(st&1) cn++;
st>>=1;
}
return cn;
}
void init()
{
for(int i = 0; i < (1 <<16);i++)
for(int j = 0; j < 20; j++)
dp[i][j] = -2e9;
}
int main()
{
int t,n;
scanf("%d",&t);
for(int ks = 1;ks <= t;ks++)
{
init();
scanf("%d",&n);
for(int i = 0; i < n; i++)
scanf("%d%d",s+i,p+i);
for(int i = 0; i < n; i++)
dp[1<<i][i] = 0;
for(int i = 1; i < (1<<n);i++)
{
for(int j = 0; j < n; j++)
{
if((i | (1<<j))!=i || (p[j] != -1 && counts(i) != p[j]+1))
continue;// 阻止非法状态向外转移
for(int k = 0; k < n;k++)
{
if((i | (1<<k)) == i) continue;
if(p[k]!= -1 && counts(i) != p[k]) continue;
int nst = i|(1<<k);
if(dp[nst][k] == -2e9)
dp[nst][k] = dp[i][j]+s[j]*s[k];
else
dp[nst][k] = max(dp[nst][k], dp[i][j]+s[j]*s[k]);
}
}
}
int ans = -2e9;
for(int i = 0; i < n; i++)
ans = max(ans,dp[(1<<n)-1][i]);
printf("Case #%d:\n%d\n",ks,ans);
}
return 0;
}