2018.8.4T2(贪心,dp,线段树,优先队列)

18 篇文章 0 订阅
16 篇文章 0 订阅

给定一个偶数 N,现在蔡老板得到了一个由 [1,N] 内的所有偶数构成的排列 b[1..N/2]
现在蔡老板又得到了一个数组 a[1..N/2],其中 a[i]=i∗2−1
蔡老板想知道,对于所有满足 a 和 b 都是它的子序列的 [1,N] 的排列 p,p 的逆序对的最小值

输入格式
第一行一个偶数 N
第二行 N/2N/2 个偶数,描述 b[1..N/2]
输出格式
输出逆序对的最小值

样例1
input
6
6 4 2
output
5


50分做法dp就不讲了
f[i][j] f [ i ] [ j ] 表示奇数项选i个偶数项选j个,瞎转移就好

考虑满分的贪心
solution①
考虑每个偶数项,他在奇数项中间肯定有个原来的位置贡献的逆序对为1
相当于我们把原来的位置移成输入的位置
每次移动有花费
反过来就是把输入花费最少移成不下降序列
我们可以用优先队列来做
代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int b[200100];
int n;
int tr[200100];
priority_queue<int>q;
int read()
{
    int sum = 0;char c = getchar();bool flag = true;
    while( c < '0' || c > '9' ) {if(c == '-') flag = false;c = getchar();}
    while( c >= '0' && c <= '9' ) sum = sum * 10 + c - 48 , c = getchar();
    if(flag)  return sum;
     else return -sum;
}
int query(int x)
{
    int sum = 0;
    for(;x;x -= x & (-x))
        sum += tr[x];
    return sum;
}
void add(int x,int v)
{
    for(;x <= n;x += x & (-x))
        tr[x] += v;
    return;
}
int main()
{
    n = read()/2;
    ll ans = 0;
    for(int i = 1;i <= n;++i) b[i] = read() / 2;
    for(int i = n;i >= 1;--i)
    {
        ans += query(b[i]);
        add(b[i] , 1);
    }
    q.push(b[1]);
    for(int i = 2;i <= n;++i)
    {
        if(q.top() > b[i])
        {
            ans += q.top() - b[i];
            q.pop();
            q.push(b[i]);
        }
        q.push(b[i]);
    }
    printf("%lld\n",ans);
    return 0;
} 

但是优先队列部分不是很好想
solution s o l u t i o n ②
我们考虑每个数字x放在i后面的贡献
=ij=1[b[j]>x]+nj=i+1[b[j]<x] = ∑ j = 1 i [ b [ j ] > x ] + ∑ j = i + 1 n [ b [ j ] < x ]
把后面那一项换一下就变成了
=ij=1[b[j]>x]+x2jj=i[b[j]<x] = ∑ j = 1 i [ b [ j ] > x ] + x 2 − ∑ j = i j [ b [ j ] < x ]
=x2+ij=1[b[j]>x][b[j]<x] = x 2 + ∑ j = 1 i [ b [ j ] > x ] − [ b [ j ] < x ]
我们考虑 x x 变成x+2
唯一有变化的就是 x+1 x + 1
会从左边变到右边,答案变为 2 − 2
也就是一个原数列中的数对答案的影响相当于是一个后缀
到这里我们就能发现答案具有单调性
然后我们就可以分开算答案就行了

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ls root * 2
#define rs root * 2 + 1
int n ;
int b[101000];
int pos[101000];
ll ans;
int minn[401000] , lazy[401000];
int tr[101000];
const int INF = 0x7fffffff;
int read()
{
    int sum = 0;char c = getchar();bool flag = true;
    while( c < '0' || c > '9' ) {if(c == '-') flag = false;c = getchar();}
    while( c >= '0' && c <= '9' ) sum = sum * 10 + c - 48 , c = getchar();
    if(flag)  return sum;
     else return -sum;
}
int query(int x)
{
    int sum = 0;
    for(int i = x;i;i -= i & (-i))
        sum += tr[i];
    return sum;
}
void add(int x,int v)
{
    for(;x <= n;x += x & (-x))
        tr[x] += v;
    return;
}
void build(int root,int l,int r)
{
    minn[root] = INF;
    if(l == r)
    {
        minn[root] = l;
        return;
    }
    int mid = (l + r)>>1;
    build(ls,l,mid);build(rs,mid+1,r);
    minn[root] = min(minn[ls],minn[rs]);
    return;
}
void init()
{
    n = read()/2;
    for(int i = 1;i <= n;++i) 
    {
        b[i] = read()/2;
        pos[b[i]] = i;
    }
    for(int i = n;i >= 1;--i)
    {
        ans += query(b[i]);
        add(b[i],1);
    }
    build(1,0,n);
    return;
}
void update(int root)
{
    minn[ls] += lazy[root];
    minn[rs] += lazy[root];
    lazy[ls] += lazy[root];
    lazy[rs] += lazy[root];
    lazy[root] = 0;
    return;
}
void change(int root,int l,int r,int x,int y,int v)
{
    if(r < x || l > y) return;
    if(l == r) 
    {
        minn[root] += v;
        return;
    }
    if(x <= l && r <= y)
    {
        minn[root] += v;
        lazy[root] += v;
        return;
    }
    update(root);
    int mid = (l + r)>>1;
    change(ls,l,mid,x,y,v);change(rs,mid+1,r,x,y,v);
    minn[root] = min(minn[ls] , minn[rs]);
    return;
}
void work()
{
    for(int i = 1;i <= n;++i)
    {
        ans += minn[1];
        change(1,0,n,0,pos[i]-1,1);
        change(1,0,n,pos[i],n,-1);
    }
    printf("%lld\n",ans);
    return;
}
int main()
{
    init();
    work();
    return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值