【ACM算法】Swaps and Inversions

Swaps and Inversions

Problem Description

Long long ago, there was an integer sequence a.
Tonyfang think this sequence is messy, so he will count the number of inversions in this sequence. Because he is angry, you will have to pay x yuan for every inversion in the sequence.
You don't want to pay too much, so you can try to play some tricks before he sees this sequence. You can pay y yuan to swap any two adjacent elements.
What is the minimum amount of money you need to spend?
The definition of inversion in this problem is pair (i,j) which 1≤i<j≤n and ai>aj .

Input


There are multiple test cases, please read till the end of input file.
For each test, in the first line, three integers, n,x,y, n represents the length of the sequence.
In the second line, n integers separated by spaces, representing the orginal sequence a.
1≤n,x,y≤100000 , numbers in the sequence are in [−109,109] . There're 10 test cases.


Output

For every test case, a single integer representing minimum money to pay.

Sample Input

3 233 666

1 2 3

3 1 666

3 2 1

Sample Output

0

3

题目大意:

很久以前,有一个整数序列a。Tonyfang认为这个序列是混乱的,所以他会计算这个序列中逆序的数量。因为他很生气,你必须为每一个逆序付出x元。你不想付太多钱,所以你可以试着在他看到这个序列之前玩一些把戏。你可以支付y元来交换任何两个相邻的元素。你需要花的最少的钱是多少?

补充:

逆序数(在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。)比如 序列 3 2 1 有 (3,1)、(3,2)、(2,1)三个逆序

分析:

由于每次交换只能在相邻的两个之间进行交换,一次交换只能消除一个逆序,因此一次交换和一个逆序是等价的,要让这个序列没有逆序就得交换它的逆序次;所以花的钱最少,取决于x和y那个大哪个小。

利用树状数组求逆序数

树状数组

C1 = A1

C2 = A1 + A2

C3 = A3

C4 = A1 + A2 + A3 + A4

C5 = A5

C6 = A5 + A6

C7 = A7

C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8

性质:

设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。因为这个区间最后一个元素必然为Ax。规律:树状数组的节点的子节点的下标小于等于其自身。

所以很明显:Cn = A(n – 2^k + 1) + ... + An

算这个2^k有一个快捷的办法,定义一个函数如下即可:

int lowbit(int x){
return x&-x;  //x与它的补码做与运算
}

生成树状数组:

//树状数组的生成

int tree[100010];
 memset(tree,0,sizeof(tree)); //初始化树所有的值为0

void add(int k,int num)
{
    while(k<=n) //如果到了最终节点结束
    {
        tree[k]+=num;
        k+=k&-k; //找父节点
    }
}
 

读取树状数组中某节点的子节点个数:

int read(int k)
{
    int sum=0;
    while(k) //k>=1
    {
        sum+=tree[k];
        k-=k&-k; //寻找子节点
    }
    return sum;
}

实例代码

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

int n,tree[100010];

//一边构造树状数组一边在当前树中 查找树中有多少比当前这个数小的数 
void add(int k,int num)
{
    while(k<=n)
    {
        tree[k]+=num;
        k+=k&-k;
    }
}

int read(int k)
{
    int sum=0;
    while(k)
    {
        sum+=tree[k];
        k-=k&-k;
    }
    return sum;
}
struct node
{
    int val,pos; //pos记录数字的位置
}a[100010];

bool cmp(node a,node b)
{
    return a.val < b.val; //从打到小排列
}
int main(void)
{
    int i,j;
    int b[100010];
    int x,y;
    long long x1;
    while(cin>>n&&n)
    {
        cin>>x;
        cin>>y;
        memset(tree,0,sizeof(tree));
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i].val);
            a[i].pos = i; //记录每个数的实际位置 
        }
        sort(a+1,a+1+n,cmp);
        int cnt = 1;
        for(i=1;i<=n;i++)
        {
            if(i != 1 && a[i].val != a[i-1].val) //保证获取的是没有重复的 
            cnt++;
            b[a[i].pos] = cnt; 
        }
        LL sum = 0;
        for(i=1;i<=n;i++)
        {
            add(b[i],1); //构造树
            sum += (i - read(b[i])); 
        }
        /*找x,y中小的那个*/
        if(x<=y)
        {
            x1=x*sum;
        }
        else {
            x1=sum*y;
        }
        cout<<x1<<endl; 
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37406764/article/details/81209927