Codeforces Round #594 (Div. 1) E - Turtle (思维+记录路径01背包)

题目链接

题目大意:

给你2*n的格子,每个格子上有一个权值。起点在左上角,终点在右下角,只能向右或者向下走。让你重新排列这些权值,使得从起点到终点的路径权值的最大值最小。(路径权值为路径经过的格子权值的累加)权值 a ≤ 5 e 4 , n ≤ 25 a\le 5e4, n\le 25 a5e4,n25

解题思路:

首先如果假设第一排已经排列好,其中一对相邻的格子权值为a和b,a > b且a在b左边。
那么所有路径中,经过a和b的情况只有三种:

  1. 都不经过
  2. 只经过a
  3. a和b都经过

那么这个时候交换a和b的权值显然不会让最大值变大(一些路径权值不变,一些路径权值变小)
因此,如果排列在第一排的元素确定了,那么它们的权值应该是从左到右递增的。
同样的方法可以分析出,第二排的元素应该是从左到右递减。
当两排元素都确定了的时候,开始选择最佳路线,其实就是选择一列i,在i处向下。
假设第一排元素为 x 1 , x 2 , . . x n , x_1,x_2,..x_n, x1,x2,..xn,第二排元素是 y 1 , y 2 . . , y n y_1, y_2..,y_n y1,y2..,yn
那么选i+1列转折比选i多出来的权值 d w = x i + 1 − y i dw=x_{i+1}-y_i dw=xi+1yi。由前面的分析可知,x递增,y递减,所以这个权值 d w dw dw是递增的,因此从左到右选择转折点,总权值的变化要么是先减后增,要么是递增。那么显然应该选1或n为转折点。
因为第一排第一个和第二排最后一个是必选,所以把最小的两个元素分配给它们。然后剩下2(n-1)个元素分成两份n-1个元素的集合,使得权值最大的那个集合权值最小。
这样就转换成了一个01背包问题:容量为sum/2,在2(n-1)个物品中选n-1个物品,能装的最大重量是多少。
d p ( i , j , k ) dp(i,j,k) dp(i,j,k)表示考虑了前i个物品,选了j个物品,能否选出权值总和为k。因为还要记录路径,所以顺便用选择的最后一个物品编号来代表可行性。
感觉这题思维很巧妙,需要想到两步:

  1. 第一排和第二排有序的
  2. 通过上一步推出,最优走法要么在起点转折要么在最后转折。

比较开心的是用这种记录路径方式最后只用了32M的空间

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 5e4 + 10;
char dp[26][maxn*25];
int a[maxn];
int main()
{
	int n; cin>>n;
	n *= 2;
	int sum = 0, num = (n-2)/2;
	for(int i = 0; i < n; ++i) scanf("%d", &a[i]), sum += a[i];
	sort(a,a+n);
	sum -= a[0] + a[1];
	int fix = sum/2;
	//cout<<"fix:"<<fix<<endl;
	dp[0][0] = -1;
	int up = 0;
	for(int i = 2; i < n; ++i){
        for(int k = min(i-2,num-1); k >= 0; --k){
            for(int j = min(up, fix-a[i]); j >= 0; --j){
                if(dp[k][j] && !dp[k+1][j+a[i]] ) {
                    dp[k+1][j+a[i]] = i;
                }
            }
        }
        up += a[i];
	}
	int ans;
	for(int i = fix; i >= 0; --i){
        if(dp[num][i]) {
            ans = i; break;
        }
	}
	int vis[55]; memset(vis, 0, sizeof vis);
	int id = dp[num][ans];
	while(id != -1){
        vis[id] = 1;
        num--; ans -= a[id];
        id = dp[num][ans];
	}
	cout<<a[0];
	for(int i = 2; i < n; ++i) if(vis[i]) cout<<" "<<a[i]; cout<<endl;
	for(int i = n-1; i >= 2; --i) if(!vis[i]) cout<<a[i]<<" "; cout<<a[1]<<endl;
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值