牛客练习赛38 E 出题人的数组(level 3)(思维乱搞)

题目链接

出题人有两个数组,A,B,请你把两个数组归并起来使得cost=∑i∗ci最小

归并要求原数组的数的顺序在新数组中不改变

n,m<=100000
ai,bi<=100000

 

解析:

这道题的思维量还是有一点大的(对于像我这种蒟蒻来说....)

这里就转一个博主的题解,讲的还是非常清楚的

其中算平均数这个就是最难想的,也是本题思维量最大的地方

转载链接

这题直接自闭了…
想了两个dp然后发现状态是O(n*n)的根本没法优化…自闭了自闭了
问了问OZY
开始就先把B接在A的后面,然后你显然每次是找一个前缀扔到A里面,并且需要满足不超过之前放的那个前缀的位置
那我们考虑他扔到的位置距离A的结束点的贡献,不妨设这个长度为y,和为s2
这个前缀的长度为x,和为s1
显然想要优秀的话,你要满足这个式子y∗s1>x∗s2
第一个是减少的贡献,第二个是增加的贡献
移项一下就可以知道,满足后面这个前缀的平均值比前面这个后缀的平均值大就是优秀的
那么由于最后的序列一定是一段A+一段B+一段A…这样的
所以你要满足这些A和这些B的段的平均值要递减
同时显然我们想让这个玩意尽量优秀的话,就要满足每次从B中拿出来的前缀的平均值尽量大
根据上面这些性质
你就把A和B分成若干段,满足平均值递减并且没有任何合并操作能使得某个段的平均值变大
然后直接按大小归并起来就可以了…

 这道题在预处理A,B数组,使其变成递减的一段段时,是需要进行回溯处理的。一开始我怕T就只考虑与当前下标i前面的那一段,但是这样贪是不行的。

4 3
2 100 3 10000
2 100 98

这组样例的A数组就是反例。

所以每更新一段的值,都要把前面所有的段遍历一遍,把该段合并到与他相邻的平均值比他小的段。

这个回溯的时间好像不是O(n)而是一个常数复杂度。

因为一旦你某一个下标i,将1..i-1都回溯了一遍,那么1...i就会被合并成完整的一段,那么对于第i+1个数,前面只需要回溯一次(前面就只有一段)就可以了。所以总的复杂度没有到O(n*n)而是O(常数*n)

#include 
using namespace std;
typedef long long ll;
 
const int MAXN = 1e5+100;
const int INF = 0x3f3f3f3f3f3f3f3f;
int a[MAXN*2][2];
 
int numA[MAXN*2];
ll sum[MAXN*2];
 
inline int Size(int num[])
{
    return num[1]-num[0]+1;
}
 
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&numA[i]);
    }
 
    for(int i=n+1;i<=n+m;i++)
    {
        scanf("%d",&numA[i]);
    }
    ll pre=INF;
    int id=-1;
 
    for(int i=1;i<=n;i++)
    {
        ll tmp;
        if(id!=-1) tmp=1ll*numA[i]*Size(a[id]);
        if(pre>tmp||id==-1)
        {
            ++id;
            a[id][0]=a[id][1]=i;
            sum[id]=numA[i];
        }
        else
        {
            sum[id]+=numA[i];
            a[id][1]=i;
        }
        pre=sum[id];
        while(id>0&&pre*Size(a[id-1])>sum[id-1]*Size(a[id]))
        {
            a[id-1][1]=a[id][1];
            sum[id-1]+=sum[id];
            id--;
            pre=sum[id];
        }
 
    }
    int cnta=id;
    pre=INF;
    for(int i=n+1;i<=n+m;i++)
    {
        ll tmp;
        if(id!=-1) tmp=1ll*numA[i]*Size(a[id]);
        if(pre>tmp||id==-1||id==cnta)
        {
            ++id;
            a[id][0]=a[id][1]=i;
            sum[id]=numA[i];
        }
        else
        {
            sum[id]+=numA[i];
            a[id][1]=i;
        }
        pre=sum[id];
        while(id>cnta+1&&pre*Size(a[id-1])>sum[id-1]*Size(a[id]))
        {
            a[id-1][1]=a[id][1];
            sum[id-1]+=sum[id];
            id--;
            pre=sum[id];
        }
 
    }
 
    ll cnt=1;
    int i,j;
    i=0;
    j=cnta+1;
    ll ans=0;
    while(i<=cnta&&j<=id)
    {
        ll costa=sum[i]*Size(a[j]);
        ll costb=sum[j]*Size(a[i]);
        if(costa>=costb)
        {
            for(int k=a[i][0];k<=a[i][1];k++)
            {
                ans=ans+(numA[k]*cnt);
                cnt++;
            }
            i++;
        }
        else
        {
            for(int k=a[j][0];k<=a[j][1];k++)
            {
                ans=ans+(numA[k]*cnt);
                cnt++;
            }
            j++;
        }
    }
    while(i<=cnta)
    {
        for(int k=a[i][0];k<=a[i][1];k++)
        {
            ans=ans+(numA[k]*cnt);
            cnt++;
        }
        i++;
    }
 
    while(j<=id)
    {
        for(int k=a[j][0];k<=a[j][1];k++)
        {
            ans=ans+(numA[k]*cnt);
            cnt++;
        }
        j++;
    }
    printf("%lld\n",ans);

 

你可能感兴趣的:(思维题)