Educational Codeforces Round 108 (Rated for Div. 2)

视频讲解地址

A-D https://www.bilibili.com/video/BV1kA411V7Sm/
E https://www.bilibili.com/video/BV1XQ4y1Z7MP/

E. Off by One

题意

平面上给定 n ( 2 e 5 ) n(2e5) n(2e5) 个点,坐标不一定为整数

接下来,你要进行一种操作:

选定两个点,将他们 纵坐标 或者 横坐标 + 1 +1 +1(两点向右向上相互独立),如果移动后,这两个点与原点共线,我们认为此操作才算有效的。操作完成,将这两点删除。

求最多的操作次数,以及它的方案

思路点

1)建图方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQj8lyBr-1619766399264)(https://z3.ax1x.com/2021/04/30/gAycIP.png)]

每各点会有两种方案。每条斜率就代表一种方案(状态)。

假设我们现在通过某种方案(下面2会说到)能够将不同方案加以区分

因为原图中每个点只能到两个状态,那么我们对他建一张新图:

​ 原图中的点 -> 新图中的边 (刚好连接的是两个点)

​ 原图中每一条斜率(方案)-> 新图中的点

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;
3)dfs答案

我们建图之后,题目等价意思就是:

求两个相邻边为一对的对数最大的方案(及其数量)

进行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';
        }
    }
}

你可能感兴趣的:(icpc,acm竞赛,c++,算法,动态规划)