题解:P13343 [EGOI 2025] 一个弦线问题
感谢来自上一篇题解的大佬 rui_er 的指教!
弦的平行
观察题目给出的两个圆形示例。
在这两个圆形中,所有共
规律
试试在图中找找规律,可以发现:在这个顺时针均匀编号的圆中,两条弦线的左右端点的编号(设其为第
注:如果
简洁地说,调整后必然会存在恰好
表示方法
为了方便表示,规定
规律应用
此时我们可以设第
我们的终极目标就是通过移动弦的端点使所有
此处用到贪心策略,步骤如下。
- 求出所有
D_i 。 - 统计这些
D_i 的出现次数。 - 求出出现次数最多的
D_i ,规定这个D_i 为“最好值”,用best 记录。 如果出现多个D_i 出现次数相同且都是最多,那么任意选择1 个D_i 作为best 值。 - 把其它所有
D_i\not=best 的弦都进行挪动操作,使其符合D_i=best 。这些弦都要挪动恰好一次,所以要最大化best ,这样一来挪动的弦的数量就会尽可能小,即K 尽可能小。
细节:特殊判断
根据上文内容,所有
那么,对于类似自测样例
求 K 的值
循环检查,从
求 K 的 50 分代码
注:题目的 SPJ 疑似与题面描述不符,这份代码可能被误判为
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int n, maxi;
struct Wire{
int x, y, sum = 0, id;
}a[N];
map <int, int> p;
int main(){
cin >> n;
for(int i=0; i<n; i++){
cin >> a[i].x >> a[i].y;
a[i].sum = (a[i].x + a[i].y) % (n*2);
if(a[i].sum % 2)
p[a[i].sum]++,
maxi = max(maxi, p[a[i].sum]);
a[i].id = i;
}
int best;
for(int i=0; i<n; i++)
if(p[a[i].sum] == maxi)
best = a[i].sum;
if(best % 2 == 0) best = 1; // 特判
int k = 0;
for(int i=0; i<n; i++)
if(a[i].sum != best) k++;
cout << k;
}
模拟:挪弦与挪弦顺序
这部分内容,要求我们必须要找到正确的挪弦顺序,否则不可能 AC。
规定已知
反推可以得到
规定
以下两种顺序被验证是错误的,但这是一种思维的过程。如果各位大佬觉得浪费时间也可以直接跳到结论。
一、先看 a 再看 b
先来考虑一种最简朴的挪动顺序:从
反之,需要考虑挪动这条弦。
先考虑
这种考虑顺序被证明是错误的,可以参考第
6
3 9
7 5
10 2
0 6
1 11
8 4
假设这组样例的 1 11)时出现
以下是这种考虑顺序对应的代码(为缩短文章长度,仅展示部分代码,不含求
for(int i=0; i<n; i++){
if(a[i].sum == best)
continue;
// x连接y 变成 x连接xx 或 y连接yy
int x = a[i].x, y = a[i].y;
int xx = (best+n*2 - x) % (n*2),
yy = (best+n*2 - y) % (n*2);
if(!ok[xx]){ // 优先考虑:如果x连接xx可以
ok[x] = ok[xx] = 1;
cout << i << ' ' << y << ' ' << xx << '\n';
}
else if(!ok[yy]){ // 否则如果y连接yy可以
ok[y] = ok[yy] = 1;
cout << i << ' ' << x << ' ' << yy << '\n';
}
}
如果将这份代码加入求
6
0 9 10
1 5 6
2 10 11
3 6 1
5 4 5
是的,面对编号为
二、dfs 确定连接顺序
开头同理。
在接下来的处理中,首先进入一层 dfs(i),如果 dfs(i+1)。
如果
如果遇到刚才提到的“都被连接”的问题,再返回(改进部分)。
显然这个写法是正确的,但必然超时。
以下给出 dfs 部分的代码,如果补全可以得到
void dfs(int i){
if(i == n){
for(int i=1; i<=cnt; i++)
cout << ans[i].id << ' ' << ans[i].s1 << ' ' << ans[i].s2 << '\n';
exit(0);
}
if(a[i].sum == best)
dfs(i + 1);
int x = a[i].x,
y = a[i].y;
int tx = match(x),
ty = match(y);
if(!ok[x] && !ok[tx]){
ok[x] = ok[tx] = 1;
ans[++cnt].id = i,
ans[cnt].s1 = y, ans[cnt].s2 = tx;
// cout << ans[cnt].id << ' ' << ans[cnt].s1 << ' ' << ans[cnt].s2 << '\n';
dfs(i + 1);
ok[x] = ok[tx] = 0;
--cnt;
}
if(!ok[y] && !ok[ty]){
ok[y] = ok[ty] = 1;
ans[++cnt].id = i,
ans[cnt].s1 = x, ans[cnt].s2 = ty;
// cout << ans[cnt].id << ' ' << ans[cnt].s1 << ' ' << ans[cnt].s2 << '\n';
dfs(i + 1);
ok[y] = ok[ty] = 0;
--cnt;
}
return;
}
三、“集体考虑”(正确顺序)
在尝试了两种错误顺序之后,我们终于迎来了正确的部分。
我们的第
6
3 6 1
4 1 2
2 2 3
0 3 4
5 4 5
1 5 6
看这个输出,我们可以发现规律:
注意:输出的各行顺序是可以调整的,所以我们发现的规律不一定是正确思路。
但这个规律也确实是一个可以尝试并且能够 AC 的代码(经尝试确认)。
AC 思路结论
详细解释这个顺序。
定义“集体”:如果若干条弦只需要相互交换端点位置,就可以让这些弦的
显然,总共
解释定义
我将使用自测样例解释“集体”。
自测样例
自测样例
大致思路
从任意一根弦出发,如果这根弦的
具体步骤
- 首先任意考虑一根弦(设其端点为
(a_1,a_2) ),调整a_1 这个端点,让这根弦的端点变成(a_2,{a_2}') ,成功。 - 如果
{a_2}' 位置原来有一根弦(设其端点为({a_2}',a_3) ),调整{a_2}' 这个端点,让这根弦的端点变成(a_3,{a_3}') ,成功。 - 重复第
2 步,让端点为({a_{i-1}}',a_i) 的弦变成(a_i,{a_i}') ,让所有弦都成功。 - 反之,对应第
2 步,如果{a_{i-1}}' 挪到的{a_i}' 位置原来没有弦,就说明不需要再调整{a_i}' 位置之前连接的弦了,这个循环模拟就暂时中断了,也就是说明这个集体都已经符合D=best 。所以退出当前循环,去寻找下一个集体,即回到第1 步。
处理集体考虑完毕的情况
观察自测样例
5
0 1
3 2
4 5
6 7
9 8
可以使用