inv 牛客网多校

https://www.nowcoder.com/acm/contest/143/D

给一个1 3 ... n-1的a序列 和一个2 4 ... n的排列的b序列 问归并后最小逆序对数 肯定考虑用a插b 因为a是有序的(无脑解释)

首先有个结论 a序列插入时 a[i+1]插入的最优位置一定在a[i]的右边

//(1,i)|(b[j]>a[i]) 代表[1,n]内有多少b[j]大于a[i]

对于每个a[i] 找一个p使(1,i)|(b[p]>a[i])+(i+1,n)|(b[p]<a[i])最小 该式子可以化为a[i]/2+( (1,i)|(b[p]>a[i])-(1,i)|(b[p]<a[i]) ) 设对于i来说 f[i][j]=( (1,i)|(b[j]>a[i])-(1,i)|(b[j]<a[i]) ) 即我们需要在f[i][1] ... f[i][n]内选一个最小值作为最优解

当i==1是 显然f[i][1]是最小的 当i>1时我们需要根据f[i-1][1] ... f[i-1][n]来得出f[i][1] ... f[i][n]

假设a[i]=2*i-1 a[i+1]=2*i+1 且b序列中2*i这个数的位置在v

(1) 对于j>=v 之前在i-1时 a[i-1]=2*i-1<b[v]=2*i 而现在变为a[i]=2*i+1>b[v]=2*i 也就是b[v]由(1,i)|(b[j]>a[i])-(1,i)|(b[j]<a[i])的左边跑的了右边 此消彼长 f[i][j]=f[i-1][j]-2

(2) 对于i<v b[v]并没有造成什么影响 故f[i][j]=f[i-1][j]

这样每次对一个后缀[v,n]减二 最后取个min即可

最巧的地方在于a是全奇 b是全偶 枚举a时每次只增加2 只用考虑夹在a[i-1]与a[i]之间的那个b值即可

至于为什么会有 a序列插入时 a[i+1]插入的最优位置一定在a[i]的右边 我的理解是因为每次对一个后缀减二 所以每次更新后的f[i]是递减的 所以最优解肯定出现在右边(强行解释)

#include <bits/stdc++.h>
using namespace std;
#define ll long long

struct node
{
    int l;
    int r;
    ll val;
    ll laz;
};

node tree[400010];
int ary[100010],pos[100010];
int n;

void buildI(int l,int r,int cur)
{
    int m;
    tree[cur].l=l;
    tree[cur].r=r;
    tree[cur].val=0;
    if(l==r) return;
    m=(l+r)/2;
    buildI(l,m,2*cur);
    buildI(m+1,r,2*cur+1);
}

ll queryI(int pl,int pr,int cur)
{
    ll res;
    if(pl<=tree[cur].l&&tree[cur].r<=pr)
    {
        return tree[cur].val;
    }
    res=0;
    if(pl<=tree[2*cur].r) res+=queryI(pl,pr,2*cur);
    if(pr>=tree[2*cur+1].l) res+=queryI(pl,pr,2*cur+1);
    return res;
}

void updateI(int tar,int cur)
{
    tree[cur].val++;;
    if(tree[cur].l==tree[cur].r) return;
    if(tar<=tree[2*cur].r) updateI(tar,2*cur);
    else updateI(tar,2*cur+1);
}

void pushup(int cur)
{
    tree[cur].val=min(tree[2*cur].val,tree[2*cur+1].val);
}

void pushdown(int cur)
{
    if(tree[cur].laz!=0)
    {
        tree[2*cur].val+=tree[cur].laz;
        tree[2*cur].laz+=tree[cur].laz;
        tree[2*cur+1].val+=tree[cur].laz;
        tree[2*cur+1].laz+=tree[cur].laz;
        tree[cur].laz=0;
    }
}

void buildII(int l,int r,int cur)
{
    int m;
    tree[cur].l=l;
    tree[cur].r=r;
    tree[cur].val=l;
    tree[cur].laz=0;
    if(l==r) return;
    m=(l+r)/2;
    buildII(l,m,2*cur);
    buildII(m+1,r,2*cur+1);
}

void updateII(int pl,int pr,int cur)
{
    if(pl<=tree[cur].l&&tree[cur].r<=pr)
    {
        tree[cur].val-=2;
        tree[cur].laz-=2;
        return;
    }
    pushdown(cur);
    if(pl<=tree[2*cur].r) updateII(pl,pr,2*cur);
    if(pr>=tree[2*cur+1].l) updateII(pl,pr,2*cur+1);
    pushup(cur);
}

int main()
{
    ll ans;
    int i;
    scanf("%d",&n);
    for(i=1;i<=n/2;i++)
    {
        scanf("%d",&ary[i]);
        pos[ary[i]/2]=i;
    }
    buildI(1,n/2,1);
    ans=0;
    for(i=1;i<=n/2;i++)
    {
        ans+=queryI(ary[i]/2,n/2,1);
        updateI(ary[i]/2,1);
    }
    //printf("***%lld***\n",ans);
    buildII(0,n/2,1);
    for(i=1;i<=n/2;i++)
    {
        ans+=((i-1)+tree[1].val);
        updateII(pos[i],n/2,1);
    }
    printf("%lld\n",ans);
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值