【JZOJ 省选模拟】Balancing Inversions

题目

Description
Bessie和Elsie在一个长为2N的布尔数组A上玩游戏(1≤N≤10^5)。Bessie的分数为A的前一半的逆序对数量,Elsie的分数为A的后一半的逆序对数量。逆序对指的是满足A[i]=1以及A[j]=0的一对元素,其中i<j。例如,一段0之后接着一段1的数组没有逆序对,一段X个1之后接着一段Y个0的数组有XY个逆序对。

Farmer John偶然看见了这一棋盘,他好奇于可以使得游戏看起来成为平局所需要交换相邻元素的最小次数。请帮助Farmer John求出这个问题的答案。

Input
输入的第一行包含NN,第二行包含2N个为0或1的整数。

Output
输出使得游戏成为平局最少需要的移动次数。

Sample Input
5
0 0 0 1 0 1 0 0 0 1

Sample Output
1

在这个例子中,初始时前一半有1个逆序对,后一半有3个逆序对。交换了第5和第6个数之后,两个子数组均有0个逆序对。

Data Constraint

思路

由于交换是相邻交换,所以分为两类:
1.左右区间内部交换,那么一定会让逆序对数量 ± 1 \pm 1 ±1,也就是说如果没有左右区间之间交换,那么答案就是 ∣ a n s L − a n s R ∣ |ansL-ansR| ansLansR(ans表示逆序对数量)
2.左右区间之间交换,考虑枚举左边最终有多少个1,不妨假设比原来多(原来少一样,但不能都异或1之后重复一遍,会错的),首先一定尽量交换左边的最右边的若干个0和右边最左边的若干个1,然后快速的去维护两边的逆序对数量
维护方式很简单,由于假如左边如果改变了一个点,说明它的右边一定都是同样的数字,所以不用线段树,只需要维护左边的前缀和即可(右边同理)

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std; 
const int N=1e5+77; 
int a[N],b[N],r0[N],l1[N]; 
int main()
{
	freopen("balance.in","r",stdin); freopen("balance.out","w",stdout);
    int n; 
    ll sa=0,sb=0,numa=0,numb=0; 
    scanf("%d",&n); 
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]); 
        if(a[i]==0)
            sa+=numa; 
        else
            numa++; 
        l1[i]=l1[i-1]+a[i]; 
    }
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&b[i]); 
        if(b[i]==0) sb+=numb; 
        else numb++; 
    }
    for(int i=n; i>=1; i--) r0[i]=r0[i+1]+(b[i]==0); 
    int pa=n,pb=1; 
    if(sa==sb) return 0*printf("0\n"); 
    ll mi=abs(sa-sb),ssa=sa,ssb=sb,yjy=0; 
    for(int i=1; ; i++)
    {
        while(pa>=1&&a[pa]!=0)
            pa--; 
        if(pa<=0)
            break; 
        while(pb<=n&&b[pb]!=1)
            pb++; 
        if(pb>n)
            break; 
        yjy+=n-pa+pb; 
        ssa-=l1[pa],ssb-=r0[pb]; 
        mi=min(mi,(ssa-ssb>=0?ssa-ssb:ssb-ssa)+yjy); 
        pa--,pb++; 
    }
    pa=n,pb=1,yjy=0; 
    for(int i=1; ; i++)
    {
        while(pa>=1&&a[pa]!=1)
            pa--; 
        if(pa<=0)
            break; 
        while(pb<=n&&b[pb]!=0)
            pb++; 
        if(pb>n)
            break; 
        yjy+=n-pa+pb; 
        sa=sa-(n-pa)+l1[pa-1],sb=sb-(pb-1)+r0[pb+1]; 
        mi=min(mi,(sa-sb>=0?sa-sb:sb-sa)+yjy); 
        pa--,pb++; 
    }
    printf("%lld\n",mi); 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值