BZOJ 4244 邮戳拉力赛 (DP)

3 篇文章 0 订阅

为了防止我的博客被数学占领(一半以上的博文和数学相关),我决定添加几道非数学题的题解。
目前数学题比例: 15 32 = 0.46875 \frac{15}{32}=0.46875 3215=0.46875
扯淡结束

题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=4244

题意: 太长了自己看。

题解:
额……这个题初一的时候好像就在模拟赛里见过。(ckw巨佬当场切掉)
考虑dp: 设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示考虑前 i i i个位置,其中有 j j j趟车开往 i i i右边的位置(不包括 i i i)的最小代价。
胡乱转移即可。

……等等……这个题解是不是不太详细……
详细解释一下。
A [ i ] A[i] A[i] 从左边进入 i i i的费用。
B [ i ] B[i] B[i] i i i到右边的费用。
C [ i ] C[i] C[i] 从右边进入 i i i的费用。
D [ i ] D[i] D[i] i i i到左边的费用。
则从 j j j i i i的费用是 w ( i , j ) = T ( i − j ) + B [ j ] + A [ i ] ( j &lt; i ) w(i,j)=T(i-j)+B[j]+A[i] (j&lt;i) w(i,j)=T(ij)+B[j]+A[i](j<i) w ( i , j ) = T ( j − i ) + D [ j ] + C [ i ] ( j &gt; i ) w(i,j)=T(j-i)+D[j]+C[i](j&gt;i) w(i,j)=T(ji)+D[j]+C[i](j>i)
然后我们拆一拆式子,以 j &lt; i j&lt;i j<i为例:
w ( i , j ) = ( T i + A [ i ] ) + ( − T j + B [ j ] ) w(i,j)=(Ti+A[i])+(-Tj+B[j]) w(i,j)=(Ti+A[i])+(Tj+B[j])
然后我们发现一个了不起的性质——贡献的式子对于 i , j i,j i,j是独立的,对于同一个 i i i来说, j j j是多少对贡献并没有影响,有影响的只是 j j j i i i的大小关系!
于是我们设计出前面所述的状态
转移比较麻烦,我们先考虑一个简化版问题:如果每个点只能经过一次?
考虑枚举当前点的 4 4 4种进出状态

  1. 左进左出,这样相当于一个原来到右边 i i i的现在变成了左边,因此右边的数量 − 1 -1 1. 从 d p [ i − 1 ] [ j + 1 ] dp[i-1][j+1] dp[i1][j+1]转移来
  2. 左进右出,从 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]转移来
  3. 右进左出,从 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]转移来
  4. 右进右出,从 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1]转移来
    注意(3)(4)两种情况要特判 j &gt; 0 j&gt;0 j>0(想一想为什么)
    那如果可以经过多次?加一个类似于完全背包的转移,详见代码。

(继续瞎扯)我做这题的时候,写完了狂WA不止,然后上网搜题解,发现全是什么括号序列,蒙蔽了。
刚准备抄标程的时候,我发现标程除了方程完全不一样,还比我多个特判。
然后我把我的方程加了这个特判。切了……

好了,那么标程是怎么写的呢?
思路是,考虑把原序列转化成括号序列。
左进左出为右括号,右进右出为左括号,然后设了一个和我差不多的dp状态。
然而转移大不相同。因为这种计算方法是答案等于所有匹配的括号的距离之和,计算每一个位置对答案的贡献就是跨过该位置的括号对个数。
总结:用括号序列表示问题是一种不错的思路。

代码实现

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define llong long long
using namespace std;
const int N = 3000;
llong a[N+3];
llong b[N+3];
llong c[N+3];
llong d[N+3];
llong dp[N+3][N+3];
int n; llong t;
int main()
{
    scanf("%d%lld",&n,&t);
    for(int i=1; i<=n; i++) scanf("%lld%lld%lld%lld",&a[i],&b[i],&c[i],&d[i]);
    memset(dp,42,sizeof(dp));
    dp[0][0] = 0ll;
    for(int i=1; i<=n; i++)
    {
        for(int j=0; j<=n; j++)
        {
         if(j)
         {
          dp[i][j] = min(dp[i][j],dp[i-1][j-1]+(-t*i+c[i])+(-t*i+b[i]));
          dp[i][j] = min(dp[i][j],dp[i-1][j]+(-t*i+c[i])+(t*i+d[i]));
         }
            dp[i][j] = min(dp[i][j],dp[i-1][j]+(t*i+a[i])+(-t*i+b[i]));
            dp[i][j] = min(dp[i][j],dp[i-1][j+1]+(t*i+d[i])+(t*i+a[i]));
        }
        for(int j=1; j<=n; j++) dp[i][j] = min(dp[i][j],dp[i][j-1]+(-t*i+c[i])+(-t*i+b[i]));
        for(int j=n-1; j>=0; j--) dp[i][j] = min(dp[i][j],dp[i][j+1]+(t*i+a[i])+(t*i+d[i]));
    }
    printf("%lld\n",dp[n][0]+t*(n+1ll));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值