题意
给你一个
2×n的棋盘,经过格子
(i,j)就会获得权值
ai,j
重新排列整个棋盘,使得从左上角走到右下角的路径权值和的最大值最小(只能向下或者向右走)
2≤n≤25,0≤ai,j≤5×104(1≤i≤2,1≤j≤n)
题解
考虑交换同一行两个数的位置对最大值的影响
变成
那么有的路径权值和不会改变,有的路径取值和会减小,这样路径最大值就有可能减小
所以第一行升序排序更优,同理可得第二行降序排列更优
考虑对于一种已经排好序的棋盘答案多少
考虑两个相邻路径的权值和的差异
x1 |
… |
xi |
xi+1 |
… |
xn |
y1 |
… |
yi |
yi+1 |
… |
yn |
对于路径
x1→...→xi→yi→...→yn和路径
x1→...→xi→xi+1→yi+1→...→yn
他们的权值差的绝对值就是
Δi=∣xi+1−yi∣,由于
x递增,
y递减
所以
Δi的函数图像就长这样
所以最大值就一定会在两边取到,即最大值为
max(∑i=1nxi+yn,x1+∑i=1nyi)=x1+yn+max(∑i=2nxi,∑i=1n−1yi)
把最小的两个数拿走,问题就转化成了:给你一个有
m个数的序列
a,将其分成两个等大的集合
A,B,最小化
max(∑A,∑B)
这个问题实质上就是一个背包问题
先把
a从小到大排序,设
Sum=∑a
设
f[i][j][k]表示前
i个数中放
j个数在集合
A中,且
∑A=k的情况是否存在
那么
f[i][j][k]=f[i−1][j][k]∣f[i−1][j−1][k−ai]
那么
Ans=min{max(k,Sum−k)},可见
Ans越靠近
2Sum越小
然后根据
Ans寻找转移路径输出答案即可
一个小优化是
f的转移可以用
C++的
bitset实现
时间复杂度
O(641n2∑a)
#include<bits/stdc++.h>
#define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
using namespace std;
const int N=55,M=50*5e4+5;
typedef long long ll;
int n,m;
bitset<M>f[52][26];
int main(){
#ifndef ONLINE_JUDGE
file("s");
#endif
scanf("%d",&n);m=(--n)<<1;
vector<int>a(m+3),Ans[2];
fp(i,1,m+2)scanf("%d",&a[i]);
sort(a.begin()+1,a.end());
Ans[0].push_back(a[1]);a.erase(a.begin()+1);
Ans[1].push_back(a[1]);a.erase(a.begin()+1);
f[0][0][0]=1;
fp(i,1,m){
f[i][0][0]=1;
fp(j,1,min(n,i))
f[i][j]=f[i-1][j]|(f[i-1][j-1]<<a[i]);
}
int Cur=accumulate(a.begin()+1,a.end(),0)>>1,Cnt=n;
while(!f[m][n][Cur])--Cur;
fd(i,m,1)
if(a[i]>Cur||!f[i-1][Cnt-1][Cur-a[i]])
Ans[0].push_back(a[i]);
else Ans[1].push_back(a[i]),Cur-=a[i],--Cnt;
sort(Ans[0].begin(),Ans[0].end());
sort(Ans[1].begin(),Ans[1].end(),greater<int>());
fp(i,0,1)fp(j,0,n)printf("%d%c",Ans[i][j]," \n"[j==n]);
return 0;
}