2020牛客暑期第三场 E-Two matchings

题意:

找到两个序列p,q,这两个序列是1-n中每两个数之间交换所得的序列(每一个数都要被交换过),给出a1-an,求 ∑ ∣ a [ i ] − a [ k [ i ] ] ∣ \sum |a[i]-a[k[i]]| a[i]a[k[i]] 的最小值,k分别是p和q序列中的一个(p和q序列都要算入求和)

方法:

通过画线段表示数字大小可以发现,将数组排序后,每四个数字可以两两交换,因此最佳情况是算上所有排序后的a[i] - a[i - 3]求和,但是假如n=10,则会出现剩下6个的情况,因此6个的情况也要算上(是6个在前好还是6个在后一组好?因此要dp时把6个的情况也算上),但是一旦到8个一分的情况,法一:下面是重点:由于8个可以分成两个四个,所以没有必要计算(dp两层和一层的效果是一样的),这种贪心思想把计算dp[i]时本来的任意长度的表达式:
d p [ i ] = m i n ( d p [ i ] , d p [ j ] + 2 ∗ ( a [ i ] − a [ j + 1 ] ) ) dp[i] = min(dp[i],dp[j] + 2 * (a[i] - a[j + 1])) dp[i]=min(dp[i],dp[j]+2(a[i]a[j+1]))
变换成了:
d p [ i ] = m i n ( d p [ i − 4 ] + 2 ∗ ( a [ i ] − a [ i − 3 ] ) , d p [ i − 6 ] + 2 ∗ ( a [ i ] − a [ i − 5 ] ) ) dp[i] = min(dp[i-4] + 2 * (a[i] - a[i - 3]),dp[i - 6] +2 * (a[i] - a[i - 5])) dp[i]=min(dp[i4]+2(a[i]a[i3]),dp[i6]+2(a[i]a[i5]))
每一种情况只有两个子情况,几乎相当于O(n)
代码如下:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;

typedef long long ll;
const int maxn = 200020;
const int INF = 0x3f3f3f3f;

ll dp[maxn],n,t,a[maxn];

int main()
{
    scanf("%lld",&t);
    while(t --)
    {
        scanf("%lld",&n);
        for(int i = 1;i <= n;i ++)
        {
            scanf("%lld", &a[i]);
            dp[i] = INF;
        }    
        sort(a + 1, a + n + 1);
        
        dp[4] = a[4] - a[1];
        dp[0] = 0;
        for(int i = 6;i <= n;i += 2)
        {
            dp[i] = min(dp[i - 4] + (a[i] - a[i - 3]),dp[i - 6] + (a[i] - a[i - 5]));
        }
        cout << 2 * dp[n] << endl;
    }
    return 0;
}

法二:注意到原始的任意长度的dp式关于j的这一维可以通过优先队列优化,即:
d p [ i ] = m i n ( d p [ i ] , m i n ( d p [ j ] − a [ j + 1 ] ∗ 2 ) + 2 ∗ a [ i ] ) dp[i] = min(dp[i],min(dp[j] -a[j + 1] * 2) +2 * a[i]) dp[i]=min(dp[i],min(dp[j]a[j+1]2)+2a[i])
而j的这一维之前都出现过,因此可以在计算i时插入队列,代码如下:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
typedef long long ll;
const int maxn = 200020;
const int INF = 0x3f3f3f3f;
  
ll dp[maxn];
ll a[maxn];
ll n,t;
priority_queue<ll,vector<ll>,greater<ll>> que;
int main()
{
    scanf("%lld",&t);
    while(t --)
    {
        scanf("%lld",&n);
        for(int i = 1;i <= n;i ++)
        {
            scanf("%lld",&a[i]);
            dp[i] = INF;
        }
             
        sort(a + 1,a + n + 1);
        dp[4] = a[4] - a[1];
        dp[6] = a[6] - a[1];
        que.push(dp[4] - a[5]);
        for(int i = 8;i <= n;i += 2)
        {
            ll tmp = que.top();
            dp[i] = min(dp[i],(ll)tmp + a[i]);
            que.push(dp[i - 2] - a[i - 1]);
        }
        while(que.size()) que.pop();
        printf("%lld\n",2*dp[n]);
    }
    return 0;
}

注意这里乘2是最后统一乘的,一开始计算的都是一倍的a[i]差值
PS:这里n的总和才2e5,因此memset肯定会超时的,这种用不到整个数组的情况要用循环初始化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值