浙江工业大学第二十届大学生程序设计竞赛

背景

考研初试成绩刚出来,等待复试中…… 为了练习一下机试,正好和老友参加一下校赛,摇来了 Hugin 和 Suzukaze。因为 Hugin 在北京,要了一个线上参赛的名额,队名沿用了去年省赛的队名。本以为大伙都已经是退役一年的废物了,没想到三人三机一通乱切就这样了:

部分题解

这里只放我知道做法的题目(有的题直接被队友秒了)。

B Puzzle: Guide Arrow

因为箭头边必选,所以先对所有的箭头边建图。建好的图 G 必然是一个有向树森林 F,否则无解。

考虑题设条件,实际上等价于:每一条箭头边都必须指向构造出的生成树的根结点(特殊结点),因此我们只需要从森林 F 中每一个出度为 0 的结点开始向外搜索,并将搜索到的结点加入构造的生成树 T,这样就能保证每条箭头边一定指向根节点。最后我们再把剩下的结点加入 T,并检查构造是否合法。

C Puzzle: Shikaku

构造方案如下:

H Game: Bloons TD

一个比较恶心的几何,非常卡常

翻译一下题意:给定一个 n 个点的简单多边形 Sq 次询问,每次询问给出二维平面上的一个点 P,询问:从点 P 出发的所有射线中,与 S 的交点个数为奇数 / 偶数的射线数量?如果是无限条就输出 oo

观察 1:当点 PS 内部时,交点数为奇数的射线有无穷多条;点 PS 外部时,交点数为偶数的射线有无穷多条。

观察 2:当且仅当这条射线经过简单多边形 S 的至少一个顶点时,才会导致

  1. PS 内部,交点数为偶数的射线
  2. PS 外部,交点数为奇数的射线

如上图两条红色射线的情况。

然后就是本题的核心观察 3:什么情况会导致观察 2 中的两种情况?注意上图中下方的红色射线 NB,这条射线与多边形有两个交点,导致奇偶性发生变化的那个特殊交点是点 D,这是因为与 D 直接相邻的两个点 C,E 在射线的同一侧,也就是说当射线通过点 D 后这条射线仍然在多边形的内部,这就会导致交点个数的奇偶性发生变化(而点 B 就不会导致奇偶性变化,因为射线通过点 B 后就来到了多边形的外部)。

综上,对于每次询问,我们只需要枚举多边形的所有顶点,并统计所有共线且同方向的顶点中(比如上方例子中的 N,D,BM,G,I,K)有几个顶点不会造成 “穿越”(也就是 D 这种顶点),如果恰好有奇数个这类顶点就会导致奇偶性的变化。

最后是代码实现,首先必须先进行一个特判:判断询问的点是否在多边形的内部,利用 ray casting 或者 winding number 就可以做到 O(n)。然后就是枚举所有的共线且同方向的顶点,如果通过极角排序实现,那么复杂度就是 O(nlogn),这会导致总复杂度为 O(qnlogn),直接 T 飞(我比赛中就是这样死的)。

正确的姿势是哈希,因为题目中的一个条件:所询问的点与多边形的边不平行,所以对于多边形上的每个顶点 Si=(xi,yi) 和所询问的点 P=(X,Y),我们只需要知道斜率 YyiXxi 就能实现 O(N) 的枚举。具体实现上可以这么做:取一个模数 P,然后 YyiXxi=(Yyi)(Xxi)1(modP),只要这个 P 取在 O(N) 的量级,那么我们就可以预处理所有 (Xxi)1(modP) 的逆元,然后拉链法哈希一下…… 非常卡常,我用邻接表模拟的拉链法 cache 裂了,换了 ac-library 里面学的 Compressed Sparse Row 才过的:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int MOD = (int)10103;
int iv[MOD], used[MOD], pre[MOD], nxt[MOD];
vector<int> start, elist;
int n, q;
void csr_init(const vector<pair<int, int>>& edges) {
fill(start.begin(), start.end(), 0);
fill(elist.begin(), elist.end(), 0);
for (const auto& e : edges) {
start[e.first + 1]++;
}
for (int i = 1; i <= MOD; i++) {
start[i] += start[i - 1];
}
auto counter = start;
for (const auto& e : edges) {
elist[counter[e.first]++] = e.second;
}
}
inline int normal(long long a) { return (a % MOD + MOD) % MOD; }
inline int mul(int a, int b) { return (int)((long long)a * b % MOD); }
int sign(ll k) {
if (k > 0) return 1;
else if (k < 0) return -1;
return 0;
}
struct point {
int x, y;
point() {}
point(int _x, int _y) :x(_x), y(_y) {}
point operator + (const point& k) const { return point(k.x + x, k.y + y); }
point operator - (const point& k) const { return point(x - k.x, y - k.y); }
void input() { cin >> x >> y; }
};
using polygon = vector<point>;
ll dot(point k1, point k2) { return 1ll * k1.x * k2.x + 1ll * k1.y * k2.y; }
ll cross(point k1, point k2) { return 1ll * k1.x * k2.y - 1ll * k1.y * k2.x; }
bool windingNumber(const polygon& p, point q) {
int res = 0;
for (int i = 0; i < n; i++) {
int j = nxt[i];
bool below = (p[i].y < q.y);
if (below != (p[j].y < q.y)) {
int ccw = sign(cross(p[i] - q, p[j] - p[i]));
if (below == (ccw > 0)) {
res += (below ? 1 : -1);
}
}
}
return res == 0;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
iv[1] = 1;
for (int i = 2; i < MOD; i++)
iv[i] = mul(iv[MOD % i], (MOD - MOD / i));
cin >> n >> q;
start.resize(MOD + 1);
elist.resize(MOD);
vector<pair<int, int>> pr(n);
vector<point> p(n);
for (auto& i : p)
i.input();
for (int i = 0; i < n; i++) {
pre[i] = (i - 1 + n) % n;
nxt[i] = (i + 1) % n;
}
while (q--) {
point k;
k.input();
fill(used, used + MOD, 0);
int op = windingNumber(p, k);
int ans_e = 0, ans_o = 0;
for (int i = 0; i < n; i++) {
p[i] = p[i] - k;
pr[i] = { mul(normal(p[i].x), iv[normal(p[i].y)]) ,i };
}
csr_init(pr);
auto calc = [&]() {
int res = 0;
for (int i = 0; i < MOD; i++) {
if (start[i] == start[i + 1]) continue;
int sp = 0, flag = 0, s = -1, ok = 1;
for (int j = start[i]; j < start[i + 1]; j++) {
int& m = elist[j];
if (used[m])
continue;
if (!flag) {
flag = 1;
s = elist[j];
}
if (1ll * p[s].y * p[m].x == 1ll * p[s].x * p[m].y && dot(p[s], p[m]) > 0) {
used[m] = 1;
int l = pre[m], r = nxt[m];
int ccw1 = sign(cross(p[l], p[m]));
int ccw2 = sign(cross(p[r], p[m]));
sp ^= (ccw1 * ccw2 == 1);
} else {
ok = 0;
}
}
res += sp;
if (!ok) i--;
}
return res;
};
if (op == 0) { // 点在内部
cout << calc() << " " << "oo\n";
} else { // 点在外部
cout << "oo " << calc() << "\n";
}
for (int i = 0; i < n; i++)
p[i] = p[i] + k;
}
return 0;
}

J Classic: CP test

先考虑 k=1 时怎么做,显然就是一个最大子段和问题,然后发现题目中 k200,因此直接冲了一发做 k 次最大子段和的贪心就过了……

K Classic: N Real DNA Pots

二分答案 K,那么就有 Kyjyixjxi,转换一下就是 yiKxiyjKxj,这就变成了一个 LIS 问题。

评论

  1. K
    Kanoon
    2023-5-8
    2023-5-08 16:52:46

    博主上岸哪了呀 |´・ω・) ノ

    • 博主
      Kanoon
      2023-8-2
      2023-8-02 9:08:37

      zju

      • K
        Kanoon
        st1vdy
        Windows Chrome 125.0.0.0
        2024-5-26
        2024-5-26 22:46:54

        记得 20 年的时候看你 cf 和 abc 的题解,那时候你博客园的主题也蛮好看的,我比你大一届,考到 bupt 了 hhh(要不要加 QQ 交流一下 ∠(ᐛ 」∠)_

        • 博主
          Kanoon
          Windows Chrome 124.0.0.0
          2024-5-26
          2024-5-27 0:07:47

          好啊,qq:1105991798

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇