题意:给你一列数,每对逆序数都要给x元,但是在给逆序数的钱前之前,你可以多次交换相邻的两个数,每次交换给y元,问最少要付多少钱。
条件:1 逆序数: 1 ≤ i < j ≤ n 而且 A[i] > A[j](可以相邻也可以不相邻)
思路:对一个无序序列进行排序,要求一次只能交换相邻的两个数,最少需要交换次数就是逆序数的对数,所以只需要求出逆序数乘以min(x,y)。一般求逆序数是有三种方法的。第三种方法才是ac的。
第一种方法:归并排序求逆序数,但是在这道题里会超时,但还是学一下。归并排序其实就是计算每个小区间的逆序数,进而得到大区间的逆序数。(归并排序 本题超时)
代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1e5+5;
long long int count1;
int num[maxn],n,y,x;
void meger(int fst,int mid,int lst)
{
int i=fst,j=mid+1,k=fst;
int b[n];
while(i<=mid&&j<=lst)
{
if(num[i]<=num[j])
{
b[k++]=num[i++];
}
else
{
b[k++]=num[j++];
count1+=mid-i+1;//计算逆序数
}
}
while(i<=mid)
b[k++]=num[i++];
while(j<=lst)
b[k++]=num[j++];
for(int h=fst;h<=lst;h++)
{
num[h]=b[h];
}
}
void merge_sort()//这里采用了非递归写法,原先以为超时是因为递归,nlogn多case
{
for(int i=1;i<n;i*=2)
{
int t=0;
while(t+i<n)
{
int mid=t+i-1;
int lst=mid+i;
if(lst>n-1)lst=n-1;
meger(t,mid,lst);
t=lst+1;
}
}
return ;
}
int main()
{
while(~scanf("%d%d%d",&n,&x,&y))
{
for(int i=0;i<n;i++)
scanf("%d",&num[i]);
count1=0;//记录逆序数
merge_sort();//归并排序
if(y<x)
printf("%lld\n",y*count1);
else
printf("%lld\n",x*count1);
}
return 0;
}
第二种方法:树状数组,适用于数据范围小的,这题数据范围10的9次方,数组最多开到500000。开一个存数据的数组,每次输入的数据都是该数组的下标,然后树状数组每次求出在它前面比它大的个数,就是该数对应的逆序数,然后全部相加就是总的逆序数。(树状数组 本题超空间)
举例:数列 5 8 3 1
数列每次加入数字后的变化 | 每次加入的数前面比它大的数的个数 |
{} | 0 |
{5} | 0 |
{5 8} | 0 |
{5 8 3} | 2 |
{5 8 3 1} | 3 |
代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1e5+5;
int c[maxn];
int n,x,y;
int lowbit(int x)
{
return x&(-x);
}
int insert_num(int i,int x)
{
while(i<=n)
{
c[i]+=x;
i+=lowbit(i);
}
return 0;
}
int getsum(int i)
{
int sum=0;
while(i>0)
{
sum+=c[i];
i-=lowbit(i);
}
return sum;
}
int main()
{
while(~scanf("%d%d%d",&n,&x,&y))
{
memset(c,0,sizeof(c));
long long int ans=0;
for(int i=1;i<=n;i++)
{
int a;
scanf("%d",&a);
insert_num(a,1);
ans+=i-getsum(a);
}
printf("%lld\n",ans*min(x,y));
}
return 0;
}
第三种方法:因为上一个记录数字大小位置的方法是通过数组的下标,数据太大的时候,开不了太大的数组,所以这次通过数组order来记录大小位置,离散化。(树状数组+离散化 ac)
代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1e5+5;//范围
int n,x,y;
struct node
{
int data;//原始数据
int order;//下标
}a[maxn];
int c[maxn];
int aa[maxn];//离散化
int lowbit(int x)
{
return x&(-x);
}
void updata(int i,int x)
{
while(i<=n)
{
c[i]+=x;
i+=lowbit(i);
}
return ;
}
int getsum(int i)
{
int sum=0;
while(i>0)
{
sum+=c[i];
i-=lowbit(i);
}
return sum;
}
bool cmp(node a,node b)
{
if(a.data==b.data)
return a.order<b.order;
return a.data<b.data;
}
int main()
{
while(~scanf("%d%d%d",&n,&x,&y))
{
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i].data);
a[i].order=i;
}
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++)
aa[a[i].order]=i;
memset(c,0,sizeof(c));
long long int ans=0;
for(int i=1;i<=n;i++)
{
updata(aa[i],1);
ans+=i-getsum(aa[i]);
}
printf("%lld\n",ans*min(x,y));
}
return 0;
}
总结:1 这次看错题了,把逆序数理解成相邻逆置的数了
2 这次学了三种求逆序数的方法,也算是进步了