原题链接
数字重排
有n个整数排成一排,有一些数字被固定在某些位置,另外的一些可以自由交换,最终达到所有相邻两数乘积的和最大的目的。
输入
第一行输入一个整数 (1 le n le 16) (1≤n≤16)。
接下来nn行,每行给出两个整数a_i(−10^4 le a_i le 10^4)、p_i(-1 le p_i lt n)ai(−104≤ai≤104)、pi(−1≤pi<n),以空格分割。
a_iai表示数字的值,p_ipi为该数字指定的位置,如果p_i = −1pi=−1,代表该数字的位置不被限制。输入保证不会为两个数字指定相同的位置。
输出
数字重新排列后最大的所有相邻两数乘积的和,即max {a_1*a_2+a_2*a_3+......+a_{n−1} * a_n }max {a1∗a2+a2∗a3+......+an−1∗an}。
样例
输入
复制6 -1 0 2 1 -3 2 4 3 -5 4 6 5输出
复制-70输入
复制5 40 -1 50 -1 30 -1 20 -1 10 -1输出
复制4600提示
子任务1,30分, 1 le n le 9 1≤n≤9
子任务2,70分,全范围。
分析
本题涉及多个状态
- 数字的选择情况(选择了哪几个数字)
- 数字放置的位置情况
- 每次放的最后一个数字是什么
- ......
但是不能开两维状态压缩(1<<16)*(1<<16)空间会爆
仔细想想,按照固定的从左向右放的规则,如果数字的选择情况已知,那么放置情况就不用单独考虑了
转移的时候只需要知道当前的最后一位数字就可以了,那么空间就是(1<<16)*16可以承受
不过细节还是很多的,比如各种初始化和边界
代码
#include<bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f; int n; int dp[1<<20][20]; int put[20],num[20],a[20],p[20]; //第一维---状态:放了哪几个数 //第二维---当前状态的最后一个数 void checkmax(int &x,int y) { if(x==-INF||x<y) { x=y; } } int main() { scanf("%d",&n); for(int i=0;i<n;i++) { scanf("%d%d",&a[i],&p[i]); if(p[i]!=-1) put[p[i]]=a[i],num[p[i]]=i; } for(int i=0;i<(1<<n);i++) for(int j=0;j<n;j++) dp[i][j]=-INF;//初始化成-INF,因为要取max if(put[0]) { for(int i=0;i<n;i++) if(p[i]==0) dp[1<<i][i]=0; } else for(int i=0;i<n;i++) if(p[i]==-1) dp[1<<i][i]=0; //第一个数怎么放置,特殊处理一下 for(int mask=0;mask<(1<<n);mask++) { int cnt=0; for(int i=0;i<n;i++) if( (1<<i)&mask ) cnt++; if(put[cnt]) { for(int lst=0;lst<n;lst++)//上一个是什么 if(dp[mask][lst]!=-INF) checkmax(dp[mask|(1<<num[cnt])][num[cnt]],dp[mask][lst]+put[cnt]*a[lst]); } else for(int i=0;i<n;i++)//现在选什么 { if( !(mask&(1<<i)) &&p[i]==-1) for(int lst=0;lst<n;lst++)//上一个是什么 if(dp[mask][lst]!=-INF) checkmax(dp[mask|(1<<i)][i],dp[mask][lst]+a[lst]*a[i]); } } int ans=-INF; for(int i=0;i<n;i++) { ans=max(ans,dp[(1<<n)-1][i]); } printf("%d",ans); return 0; }