HDU6976. Game on Plane

题意:

Alice和Bob在玩一个游戏。在这个游戏中,有一个二维平面,上面有\(n\)​条直线。Alice首先会选择其中的\(k\)​条直线\(l_1,l_2,...,l_k\)​,然后Bob会画一条直线\(L\)​。Bob的罚分被定义为在直线\(l_1,l_2,...,l_k\)​中与\(L\)​至少存在\(1\)​个公共点的直线数量。注意,两条重合的直线也满足至少存在\(1\)​个公共点。

Alice想要使Bob的罚分最多,而Bob想要使自己的罚分最少。给定\(n\),以及这\(n\)条直线,若双方的策略都是最优的,分别求出在\(k=1,2,3,...,n\)的条件下Bob​​​的罚分。

分析:

首先Bob画的线肯定不会和某一条线重合,因为它可以选择平行,来使自己的罚分至少减1。其次Bob为了使自己罚分尽量少,肯定会选择与尽量多的直线平行。再次,Alice为了使Bob的罚分尽量多,肯定会尽量选择相互不平行的直线。故在选择直线时,如果把方向相同的直线看成一类的话,Alice可以选之前选过次数最少的那类直线。假如这\(n\)​​条直线被分为\(m\)​​类不同的直线,记作\(L_1,L_2,...,L_m\)​​的话,Alice可以先按\(L_1,L_2,...,L_m\)​​的顺序选择,每一类选择\(1\)​​个,当\(m\)​​种直线都选择过之后(称为一轮操作),再重新从\(L_1\)​​开始(开始下一轮操作),如此往复,假如当前选择的这类直线数量已经没得选了,那就跳过这类直线,选下一类直线。显然在进行第\(j\)轮操作时,若此时已经选了\(i\)​条直线,​​那么\(i\)条直线里最多有\(j\)条是相互平行(重合)的,此时答案为\(i-j\)​​​。

代码:

标程做法:
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <utility>
using namespace std;
const int maxn = 1e5 + 10;
pair<int, int> pts[maxn];
int f[maxn];
int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
void solve() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        int x1, y1, x2, y2;
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        int dx = x2 - x1;
        int dy = y2 - y1;
        if (dx == 0)
            dy = 1;
        else if (dy == 0)
            dx = 1;
        else {
            int g = gcd(abs(dx), abs(dy));
            if (dx < 0) dx = -dx, dy = -dy;
            dx /= g, dy /= g;
        }
        pts[i] = {dx, dy};
    }
    sort(pts + 1, pts + 1 + n);
    for (int i = 1; i <= n; i++) f[i] = 0;
    // f[k]表示第k轮有多少条直线可选
    for (int i = 1, j; i <= n; i = j) {
        for (j = i; j <= n && pts[i] == pts[j]; j++)
            ;
        for (int k = 1; k <= j - i; k++) f[k]++;
    }
    // 计算k = i时,同时维护j,表示进行到了第j轮
    // 进行第j轮意味着,最多有j条直线方向相同,答案即为i - j
    for (int i = 1, j = 1; i <= n; i++) {
        while (f[j] == 0) j++;
        f[j]--;
        printf("%d\n", i - j);
    }
}
int main() {
    int T;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}

标程做法还是比较巧妙的,我想不到这样的做法。

我的做法:
#include <algorithm>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <utility>
using namespace std;
const int maxn = 1e5 + 10;
pair<int, int> pts[maxn];
int f[maxn], tot, nxt[maxn];
int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
void solve() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        int x1, y1, x2, y2;
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        int dx = x2 - x1;
        int dy = y2 - y1;
        if (dx == 0)
            dy = 1;
        else if (dy == 0)
            dx = 1;
        else {
            int g = gcd(abs(dx), abs(dy));
            if (dx < 0) dx = -dx, dy = -dy;
            dx /= g, dy /= g;
        }
        pts[i] = {dx, dy};
    }
    sort(pts + 1, pts + 1 + n);
    tot = 0;
    // f[i]表示第i类线的数量
    for (int i = 1, j; i <= n; i = j) {
        for (j = i; j <= n && pts[i] == pts[j]; j++)
            ;
        f[++tot] = j - i;
    }
    // 使用链表模拟
    for (int i = 1; i < tot; i++) nxt[i] = i + 1;
    // 增加0这一个节点用以判断是否一轮操作已经结束
    nxt[tot] = 0, nxt[0] = 1;
    for (int i = 1, j = 1, k = 1, pre = 0; i <= n; i++) {
        f[k]--;
        // 如果该节点已经减为0,则删除该节点
        if (f[k] == 0)
            nxt[pre] = nxt[k];
        else
            pre = k;
        k = nxt[k];
        printf("%d\n", i - j);
        // 如果下一个节点移动到了0,则表示一轮操作已结束,j++,并移到0的下一个结点
        if (k == 0) {
            k = nxt[0];
            pre = 0;
            j++;
        }
    }
}
int main() {
    int T, kase = 0;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}