- Luogu P1012
- CF2024C
就以先 CF2024C 来展开,题意是给定 个二元组,确定一个可行的排列使得最后的序列逆序对个数最少,注意二元组内部不可以交换顺序
详情见 “CF980 Review” 中对这道题的解法,这里不多赘述了。
只是观察这种解法这道题有什么性质:
首先,我们定义的 比 更优, 应该放在前面,是指 中的较大元素比 中的较大元素更小,或者说两者的较大值相等,但是前者的较小值更小。
按照我们的方法来交换相邻元素一定不会让答案变得更劣。
不难观察到 如果 比 优,且 比 优,那么一定能够得出 比 优。
| #include<bits/stdc++.h> |
| using namespace std; |
| template<typename T>inline void re(T &x) |
| { |
| x=0;int f=1;char c=getchar(); |
| while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} |
| while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} |
| x*=f; |
| } |
| template<typename T>inline void wr(T x) |
| { |
| if(x<0)putchar('-'),x=-x; |
| if(x>9)wr(x/10); |
| putchar(x%10^48); |
| } |
| int T,n,m; |
| const int N=2e5; |
| pair<int,int> a[N]; |
| inline bool cmp(pair<int,int> x,pair<int,int> y) |
| { |
| int mx1,mn1,mx2,mn2; |
| mx1=max(x.first,x.second),mx2=max(y.first,y.second); |
| mn1=min(x.first,x.second),mn2=min(y.first,y.second); |
| return (mx1==mx2?mn1<mn2:mx1<mx2); |
| } |
| int main() |
| { |
| re(T); |
| while(T--) |
| { |
| re(n); |
| for(register int i=1;i<=n;++i) |
| { |
| re(a[i].first),re(a[i].second); |
| } |
| sort(a+1,a+n+1,cmp); |
| for(register int i=1;i<=n;++i) |
| wr(a[i].first),putchar(' '),wr(a[i].second),putchar(' '); |
| putchar('\n'); |
| } |
| return 0; |
| } |
官方题解给出了一种更加简洁而且非常容易猜到的排序方式,虽然思维上面还是小有难度,但事实上看来赛时很多人都通过了这道题,这也印证了 oi 比赛不需要严格的证明,如果你猜的结论是正确的,那么就大胆地去应用,错了反正还可以再来,只是要罚时而已 (仅限非oi赛制)
我们只需要按照 这个二元组中两个元素的和把这个二元组序列升序排列就好。
证明可以通过分类讨论的方式来实现,
如果说 的和大于 的和,那么要么 的两个值被夹在 的中间,要么 两个值 都比 的大。
在这两种情况下,我们交换 和 一定至少不会使得答案变得更差。
而且,二元组内部的和的大小,如果 比 优, 比 优,就一定有 比 优。
| #include<bits/stdc++.h> |
| using namespace std; |
| template<typename T>inline void re(T &x) |
| { |
| x=0;int f=1;char c=getchar(); |
| while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} |
| while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} |
| x*=f; |
| } |
| template<typename T>inline void wr(T x) |
| { |
| if(x<0)putchar('-'),x=-x; |
| if(x>9)wr(x/10); |
| putchar(x%10^48); |
| } |
| int T,n,m; |
| const int N=2e5; |
| pair<int,int> a[N]; |
| inline bool cmp(pair<int,int> x,pair<int,int> y) |
| { |
| return x.first+x.second<y.first+y.second; |
| } |
| int main() |
| { |
| re(T); |
| while(T--) |
| { |
| re(n); |
| for(register int i=1;i<=n;++i) |
| { |
| re(a[i].first),re(a[i].second); |
| } |
| sort(a+1,a+n+1,cmp); |
| for(register int i=1;i<=n;++i) |
| wr(a[i].first),putchar(' '),wr(a[i].second),putchar(' '); |
| putchar('\n'); |
| } |
| return 0; |
| } |
那么你会问 ,我为什么不能对于相邻的两个元素,直接比较他们交换位置前后的逆序对数量来定义他们的 优劣 呢?
而且好像似乎也满足 如果 比 优, 比 优,就一定有 比 优 的性质。
那我们再仔细思考一下,以上两种解法,我们实际上并没有考虑 的情况,因为我们认定他们无论是否交换,对答案是不会存在影响的。
在解法 1 中, 则意味着两个二元组完全相等,所以显然没有影响。
在解法 2 中, 则意味着两个二元组要么是相同,要么是完全包含,可以证明如果有 或者 ,就一定有 。
但是在这种解法中,如果 ,就不一定有 成立,一组hack数据如下。
4 7 ,2 9,7 3
| #include<bits/stdc++.h> |
| using namespace std; |
| int check(int x,int y,int i,int j) |
| { |
| return (x>i)+(x>j)+(y>i)+(y>j); |
| } |
| int main() |
| { |
| int T=300; |
| while(T--) |
| { |
| int x=rand()%10+1,y=rand()%10+1,i=rand()%10+1,j=rand()%10+1,n=rand()%10+1,m=rand()%10+1; |
| int ans1=check(x,y,i,j),ans2=check(i,j,x,y); |
| if(ans1>ans2)swap(x,i),swap(y,j); |
| ans1=check(i,j,n,m),ans2=check(n,m,i,j); |
| if(ans1>ans2)continue; |
| if(!(check(x,y,n,m)<=check(n,m,x,y))) |
| printf("%d %d %d %d %d %d\n",x,y,i,j,n,m); |
| } |
| return 0; |
| } |
解决这类问题的三个要素:
- 我们定义的交换相邻元素的方法不会使得答案变得更劣
- 我们定义的优劣比较具有传递性,更形式化地说,如果 ,那么必须有 。
- 务必要考虑等号的情况。
(注:上文的 “” 代表 "前者比后者优")
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!