洛谷P1966 火柴排队——题解

P1966 火柴排队

题解:

  将总距离表达式展开,尝试化简。发现平方的和是不变的,变的只有乘积,使Σaibi最大即可保证距离最小,自然想到最大的a让最大的b乘的贪心想法,即最终的顺序是a和b都从小到大(或从大到小)一一对应。尝试证明。

  对于顺序的贪心证明,可从一步步调整入手。即假设存在ax>ay,bx>by,若x与x相对,答案为axbx+ayby;若x与y相对,则答案为axby+aybx,作差提取公因式可发现前者大于后者,故知答案序列必为顺序对应。

  工作没有结束,题目并不要求最小的距离值,而是最小相邻交换次数。发现两个序列的交换是独立的(寻找性质),即可以先完成第一个序列的所有交换,再完成第二个序列的所有交换。找到这个性质后可以很大程度上简化思考(不然容易想破头皮也得不出结论)。对于同时处理两个序列的交换,我们还是更倾向于处理一个序列的交换。故尝试证明两个序列的交换可以等价到一个序列的交换。记两个序列分别为A、B序列,在做完A序列的交换后,发现接下来要在B序列做的所有交换按顺序对A序列做的话,最终A、B序列的对应关系是一样的,故我们只处理一个序列交换即可。

  由于序列可以离散化,且离散化后两序列值域一样,不妨做个离散化,将序列中数的对应关系简化为相等关系,那么现在问题变为求一个序列A排成另一个序列B的最小交换次数。再做个简化,我们给B序列上的数从第一个位置开始按顺序标上编号,再把A序列的值改为这个值在B序列上的编号,则问题又转化为求一个序列A从小到大排成顺序的最小交换次数。

  接下来一段时间可能想不出什么新东西了,不妨分析一下题目提到的操作:交换相邻两个数。思考这个操作在与题目有关的知识点上的意义:在排序里,交换相邻两个数可以消灭且只消灭一个相邻异序对。于是我们又找到了新的入手点。最后的顺序的异序对的个数为0,而一个操作最多消灭一个相邻异序对。异序对一定会相邻吗?思考一下,发现若存在异序对,则必会存在一个相邻异序对(考虑一对异序对a,b,设a,b之间没有相邻异序对,则a,b之间的数必从小到大排好了序,发现这个有序区间的端点必定有一个会与a或b组成一个相邻异序对),可发现最少操作次数即等于异序对个数。可用归并排序nlogn时间求出。

  时间复杂度:O(n log n)

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 
 5 using namespace std;
 6 
 7 const int N=1e5+5;
 8 
 9 struct node{
10     int num,ord,li;
11 }a[N],b[N];
12 
13 inline int read()
14 {
15     int x=0;
16     char ch=getchar();
17     while(!isdigit(ch))
18         ch=getchar();
19     while(isdigit(ch))
20         x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
21     return x;
22 }
23 
24 int n,xord[N],xu[N],ins[N];
25 
26 long long mod=1e8-3,ans;
27 
28 inline bool cmp1(const node &a,const node &b)
29 {
30     return a.num<b.num;
31 }
32 
33 inline bool cmp2(const node &a,const node &b)
34 {
35     return a.ord<b.ord;
36 }
37 
38 long long rev(int l,int r)
39 {
40     if(l==r)
41         return 0;
42     if(l+1==r)
43     {
44         if(xu[l]>xu[r])
45         {
46             swap(xu[l],xu[r]);
47             return 1;
48         }
49         return 0;
50     }
51     int mid=(l+r)>>1;
52     long long ret=rev(l,mid)+rev(mid+1,r);
53     for(int i=l;i<=r;++i)
54         ins[i]=xu[i];
55     int ll=l,rr=mid+1,cnt=l;
56     while(ll<=mid&&rr<=r&&cnt<=r)
57     {
58         if(ins[ll]<ins[rr])
59             xu[cnt++]=ins[ll++];
60         else
61         {
62             xu[cnt++]=ins[rr++];
63             ret+=mid-ll+1;
64         }
65     }
66     if(cnt<=r)
67     {
68         while(ll<=mid)
69             xu[cnt++]=ins[ll++];
70         while(rr<=r)
71             xu[cnt++]=ins[rr++];
72     }
73     return ret%mod;
74 }
75 
76 int main()
77 {
78     n=read();
79     for(int i=1;i<=n;++i)
80         a[i].num=read(),a[i].ord=i;
81     for(int i=1;i<=n;++i)
82         b[i].num=read(),b[i].ord=i;
83     sort(a+1,a+n+1,cmp1);
84     sort(b+1,b+n+1,cmp1);
85     for(int i=1;i<=n;++i)
86         a[i].li=b[i].li=i;
87     sort(a+1,a+n+1,cmp2);
88     sort(b+1,b+n+1,cmp2);
89     for(int i=1;i<=n;++i)
90         xord[a[i].li]=i;
91     for(int i=1;i<=n;++i)
92         xu[i]=xord[b[i].li];
93     printf("%lld",rev(1,n));
94     return 0;
95 }

什么?你不会归并排序求逆序对?戳:逆序对小记

总之,这道题目在很多方面用了非常多简化虽然很毒瘤,但一步步化到最后的感觉真的很奇妙

原文地址:https://www.cnblogs.com/InductiveSorting-QYF/p/13620787.html