题目:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5474
题意:
按1-2n的编号给定2n个点以及n条线段,每个点属于且仅属于一条线段。
线段用端点的编号确定。
现在一次操作可以交换两个点的编号,
要求在不超过n+10次的操作后使得任意两条线段不相交。
输入保证任意两个点不重合,任意三个点不相交。
思路:
首先要明确,不是要求最少操作次数,只要不超过n+10次就可以。
所以一定有解:
step 1: 点配对
把所有点按水平序排好,人为配对:排名2k-1的点和排名2k的点组成一条线段。(2k-1并不是点的编号,只是排序后的位置)
这样的话没有线段会相交。
step 2: 统计操作次数
点已经配好了,只要计算从原来的配对变成现在的配对需要多少次操作。
假设排序后的点编号是:a, b, ... , c, ... , d , ....
本来是a和c组成一条线段,b和d组成一条线段
为了让a和c配对,交换b和c的编号(其实是交换b和c对应的点坐标。。。)
显然最多进行n次操作
总之step 1比较简单,step 2我讲不清楚。。。
把握一点:操作之后,点的编号对应关系不变。
比如说1号点一开始与4号点组成线段,操作之后还是1号点和4号点组成线段
但是1号点和4号点的坐标可能和之前不一样
理解这一点就好了。。。
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 200000;
int a[N+10], rk[N+10], f[N+10];
int ansx[N+10], ansy[N+10];
int n;
struct point{
int x, y, id;
void input(){
scanf("%d %d", &x, &y);
}
}p[N+10];
bool cmp(point a, point b){
if (a.x == b.x) return a.y < b.y;
return a.x < b.x;
}
int main(){
int test;
scanf("%d", &test);
while (test--){
scanf("%d", &n);
for (int i=1; i<=2*n; i++){
p[i].input();
p[i].id = i;
}
int a, b;
for (int i=1; i<=n; i++){
scanf("%d %d", &a, &b);
f[a] = b; f[b] = a;
}
int nn = 2*n;
sort(p+1, p+n*2+1, cmp);
for (int i=1; i<=nn; i++)
rk[p[i].id] = i;
int cnt = 0;
for (int i=1; i<=nn; i+=2){
int a = p[i].id, b = p[i+1].id;
if (f[a] != b){
int c = f[a], d = f[b];
cnt++;
ansx[cnt] = b; ansy[cnt] = c;
swap(p[i+1].id, p[rk[c]].id);
swap(rk[b], rk[c]);
}
}
printf("%d\n", cnt);
for (int i=1; i<=cnt; i++)
printf("%d %d\n", ansx[i], ansy[i]);
}
return 0;
}