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;
}