HDU 4960 Another OCD Patient(DP)

题意就是给你5000个数字,然后还有合并i个数字所需要的花费,然后每个数字只能合并1次,要把这串数字通过合并成为对称的数字,问你最小花费。

做法:用2个数组去分别保存从左往右以及从右往左对称的地方的长度,也就是比如1 3 4 3 1 2 2 ,b[0] = 2,b[1] = 1,c[0] = 1, c[1] = 2。

在预处理过后,数字就变得没有用了,有用的就是这2个数组,但是这里会出现一个情况就是中间某一段不能匹配成为相等的,也就是说必须要合并成1个数。

假设有k个匹配的段,dp[i]等于min{ 2个数组的 i 到 k 的数字全部合并一起的花费加上dp[k]}。这里推完后dp[k-1]加上前面说的中间那段不能匹配的花费就是解了。

等一下,这样并不对。因为可能最优解是中间那段加上左右2段匹配的合在一起再加上dp得到的。所以还需要推一遍,从中间开始推,ans = min{中间那段加上左右2段的数字个数合起来的花费加上dp[i]}。这点很容易想不到。

AC代码:

#include<cstdio>
#include<ctype.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<cstdlib>
#include<stack>
#include<cmath>
#include<queue>
#include<set>
#include<ctime>
#include<string.h>
#include<string>
using namespace std;
#define ll __int64
#define eps 1e-8
ll a[5005];
int cost[5005];
int b[5005],c[5005];
ll dp[5005];
template<class T>
inline void scan_d(T &ret) {  
    char c; ret=0;  
    while((c=getchar())<'0'||c>'9');  
    while(c>='0'&&c<='9') ret=ret*10+(c-'0'),c=getchar();  
}  
int main()
{
    #ifdef GLQ
    freopen("input.txt","r",stdin);
//    freopen("o.txt","w",stdout);
    #endif // GLQ
    int n,i,j;
    while(~scanf("%d",&n)&&n)
    {
        for(i = 0; i < n; i++) scan_d(a[i]);
        for(i = 1; i <= n; i++) scan_d(cost[i]);
        i = 1;j = n-2;
        int k = 0,nct1=1,nct2=1;
        ll t1 = a[0], t2 = a[n-1];
        int oldi=0,oldj=n-1,flag = 0;
        while(1)
        {
            if(t1 > t2)
            {
                t2 += a[j--];
                nct2++;
            }
            else if(t2 > t1)
            {
                t1 += a[i++];
                nct1++;
            }
            else if(t2 == t1)
            {
                b[k] = nct1;
                c[k++] = nct2;
                nct1=nct2=1;
                oldi = i;
                oldj = j;
                if(i==j+1) break;
                t1=a[i++];
                t2=a[j--];
            }
            if(j < i-1)
            {
                flag = 1;
                break;
            }
        }
        ll jia=0;
        if(flag) jia = cost[oldj-oldi+1];
        memset(dp,0,sizeof(dp));
//        for(i = 0; i < k; i++) cout<<b[i]<<" "<<c[i]<<endl;
        int temp1=0,temp2=0;
        for(i = 0; i < k; i++)
        {
            dp[i] = (ll)cost[temp1+b[i]]+(ll)cost[temp2+c[i]];
            temp1 = b[i]; temp2 = c[i];
            for(j = i-1; j >= 0; j--)
            {
                dp[i] = min(dp[i],(ll)cost[temp1]+(ll)cost[temp2]+dp[j]);
                temp1 += b[j]; temp2 += c[j];
            }
//            cout<<temp1<<" "<<temp2<<endl;
//            cout<<dp[i]<<endl;
        }
        ll ans = min(dp[k-1]+(ll)jia,(ll)cost[n]);
        if(k == 0)
        {
            printf("%d\n",cost[n]);
            continue;
        }
        int w1 = 0,w2 = 0;
        for(i = k-1; i >= 0; i--)
        {
            ans = min(ans,dp[i]+(ll)cost[oldj-oldi+1+w1+w2]);
            w1 += b[i]; w2 += c[i];
        }
        printf("%I64d\n",ans);
    }
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值