A-D https://www.bilibili.com/video/BV1kA411V7Sm/
E https://www.bilibili.com/video/BV1XQ4y1Z7MP/
平面上给定 n ( 2 e 5 ) n(2e5) n(2e5) 个点,坐标不一定为整数
接下来,你要进行一种操作:
选定两个点,将他们 纵坐标 或者 横坐标 + 1 +1 +1(两点向右向上相互独立),如果移动后,这两个点与原点共线,我们认为此操作才算有效的。操作完成,将这两点删除。
求最多的操作次数,以及它的方案
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQj8lyBr-1619766399264)(https://z3.ax1x.com/2021/04/30/gAycIP.png)]
每各点会有两种方案。每条斜率就代表一种方案(状态)。
假设我们现在通过某种方案(下面2会说到)能够将不同方案加以区分
因为原图中每个点只能到两个状态,那么我们对他建一张新图:
原图中的点 -> 新图中的边 (刚好连接的是两个点)
原图中每一条斜率(方案)-> 新图中的点
我们通过什么方式能够避免浮点数,又能够特殊处理斜率不为0或者不存在的情况嘞?
我们可以观察到,既然给我们的点坐标是分数,我们应该考虑通分再除gcd的方式来处理
你通分后,分母是相同的,斜率本质就是分子在相除
例如:
( 1 4 , 1 2 ) 与 ( 1 8 , 1 4 ) (\frac{1}{4} ,\frac{1}{2} )与 (\frac{1}{8} ,\frac{1}{4} ) (41,21)与(81,41)他们的斜率相同
通分后,前者: ( 2 , 4 ) , g c d = 2 (2,4),gcd=2 (2,4),gcd=2 后者: ( 4 , 8 ) , g c d = 4 (4,8),gcd=4 (4,8),gcd=4
那么很显然,最后处理完的结果都是 ( 1 , 2 ) (1,2) (1,2)
我们如果要对 纵坐标 或者 横坐标 +1 那么无非就给它加 分母 即可
cin >> a[i] >> b[i] >> c[i] >> d[i];
ll x = a[i] * d[i], y = b[i] * c[i], w = b[i] * d[i]; // 通分
ll g1 = __gcd(x + w, y);
ll g2 = __gcd(x, y + w);
mp[{
(x + w) / g1, y / g1}] = 0;
mp[{
x / g2, (y + w) / g2}] = 0;
我们建图之后,题目等价意思就是:
求两个相邻边为一对的对数最大的方案(及其数量)
进行dfs搜索,我们搜的是点,看的是边,边的标号是从 0 - n-1
假设我们现在dfs到了点u,fa->u的边编号 i_par
如果,u往孩子的边可以选择的数量有偶数条,那么u父亲fa到u的这条边 i_par 不选
否则,为奇数,那我们就把 i_par 这条选上即可
声明,此代码非本人所写,仅供学习使用
#include
using namespace std;
#define rep(i,n) for(int i = 0; i < int(n); ++i)
#define rrep(i,n) for(int i = int(n)-1; i >= 0; --i)
#define all(x) (x).begin(), (x).end()
#define rall(x) (x).rbegin(), (x).rend()
template<class T> void chmax(T& a, const T& b) {
a = max(a, b);}
template<class T> void chmin(T& a, const T& b) {
a = min(a, b);}
using ll = long long;
using P = pair<ll,ll>;
using VI = vector<int>;
using VVI = vector<VI>;
using VL = vector<ll>;
using VVL = vector<VL>;
int n;
vector<vector<pair<int, int>>> to; // 用邻接表存图,<点,对应边的编号>
bool visited[1000000]; // visit[index]: 编号为 index 的点是否访问过
VVI ans;
bool used[1000000]; // used[index]: 编号为 index 的选择是否选择过
bool dfs(int u, int i_par = -1) {
visited[u] = true;
for(auto [v, i]: to[u]) if (i != i_par && !used[i]) {
if (visited[v]) {
ans[u].push_back(i), used[i] = true;
} else {
bool use = dfs(v, i);
if (!use) ans[u].push_back(i), used[i] = true;
}
}
if (ans[u].size() % 2) {
if (i_par != -1) {
ans[u].push_back(i_par), used[i_par] = true;
return true;
} else {
ans[u].pop_back();
}
}
return false;
}
int main() {
cin >> n;
VL a(n), b(n), c(n), d(n);
map<P, int> mp;
rep(i, n) {
cin >> a[i] >> b[i] >> c[i] >> d[i];
ll x = a[i] * d[i], y = b[i] * c[i], w = b[i] * d[i]; // 通分
ll g1 = __gcd(x + w, y);
ll g2 = __gcd(x, y + w);
mp[{
(x + w) / g1, y / g1}] = 0;
mp[{
x / g2, (y + w) / g2}] = 0;
}
int sz = 0;
for(auto& [k, v]: mp) v = sz++; // 分配编号
to.resize(sz);
rep(i, n) {
ll x = a[i] * d[i], y = b[i] * c[i], w = b[i] * d[i];
ll g1 = __gcd(x + w, y);
ll g2 = __gcd(x, y + w);
int u = mp[{
(x + w) / g1, y / g1}];
int v = mp[{
x / g2, (y + w) / g2}];
to[u].emplace_back(v, i); // u 与 v 间无向边,表示原图中点 i 能到的状态有u,v
to[v].emplace_back(u, i);
}
ans.resize(sz);
rep(i, sz) if (!visited[i]) dfs(i);
int cnt = 0;
for(auto& a: ans) cnt += a.size() / 2;
cout << cnt << '\n';
for(auto& a: ans) {
int n = a.size();
for(int i = 0; i < n; i += 2) {
cout << a[i] + 1 << ' ' << a[i + 1] + 1 << '\n';
}
}
}