[ZJOI2008] 无序运动 题解
一道蛮模版的字符串题,只需要一步非常显然的转化。
首先思考如果无视四种操作该怎么做,如果将每一个点看成是一个字符,那么就是给定一个文本串,多个模式串,求每个模式串在文本串里出现几次,这显然是一个字符串的模板题,可以使用 AC 自动机,也可以使用诸如后缀数组、后缀树、后缀自动机之类的后缀数据结构维护,这里不多做赘述,可以转到你谷模板题自行学习。
那么就是转化四种操作,考虑平移和旋转就是无视了绝对位置,缩放就是不关心绝对距离,翻转我们最后处理。
既然不关心绝对距离和绝对位置,我们就可以考虑现将相邻两点连线,然后存向量,转化为向量,发现再绝对的数值失去作用后,我们只能将相邻两向量的关系存下来,显然我们需要存长度的比值和夹角,如果用 double
存精度肯定没有保证,我们可以选择使用整数存下来,长度的比值我们可以直接以分数形式记录,夹角我们知道任意一个角都可以用一组正弦和余弦值唯一确定,而再向量中,我们知道点积和叉积是与正余弦相关的,但是我们只关心夹角,同理我们将正弦和余弦约分,即同时除以它们的最大公因数,正确性证明显然。
那么我们对于相邻的三个点求出两个向量,再对两个向量求出长度的比值和点叉积的比值,以四元组形式存下来作为“字符”,可以用 c++ 内置的 tuple
存储,各种自动机的转移边可以利用 map
来实现。
这里我选择用符合思维惯性的后缀自动机实现,在翻转方面,直接将给定的模式串原串翻转后重新求四元组序列,如果和原串的四元组序列相同(这种情况即给定的所有点共线),则不需要再管翻转,否则再跑一边,将两次的答案加起来,显然不会有重,注意特判只给了两个点的情况。
c++ 代码
#include<bits/stdc++.h>
using namespace std;
#define Reimu inline void // 灵梦赛高
#define Marisa inline int // 魔理沙赛高
#define Sanae inline bool // 早苗赛高
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> Pii;
typedef tuple<int, int, int> Tiii;
#define fi first
#define se second
template<typename Ty>
Reimu clear(Ty &x) { Ty().swap(x); }
typedef tuple<LL, LL, LL, LL> Data;
const int N = 200010;
int n, m;
vector<tuple<LL, LL, LL, LL>> a[N][2];
struct Vec {
int x, y;
friend istream &operator>>(istream &cin, Vec &a) { return cin >> a.x >> a.y; }
friend Vec operator-(Vec a, Vec b) { return {a.x - b.x, a.y - b.y}; }
friend LL operator|(Vec a, Vec b) { return 1LL * a.x * b.x + 1LL * a.y * b.y; }
friend LL operator&(Vec a, Vec b) { return 1LL * a.x * b.y - 1LL * a.y * b.x; }
};
inline Data calc(Vec a, Vec b, Vec c) {
a = b - a; b = c - b;
LL A = a | a, B = b | b, C = a | b, D = a & b, t1 = __gcd(A, B), t2 = __gcd(abs(C), abs(D));
return {A / t1, B / t1, C / t2, D / t2};
}
struct SAM {
int sz = 1, las = 1;
int len[N << 1], fa[N << 1], cnt[N << 1], t[N << 1], id[N << 1];
map<Data, int> ch[N << 1];
Reimu operator+=(Data o) {
int p = las; len[las = ++sz] = len[p] + 1; ++cnt[las];
while (p && !ch[p].count(o)) ch[p][o] = las, p = fa[p];
if (!p) return void(fa[las] = 1);
int q = ch[p][o];
if (len[q] == len[p] + 1) return void(fa[las] = q);
len[++sz] = len[p] + 1; ch[sz] = ch[q];
fa[sz] = fa[q]; fa[q] = fa[las] = sz;
while (p && ch[p][o] == q) ch[p][o] = sz, p = fa[p];
}
Reimu operator~() {
for (int i = 1; i <= sz; ++i) ++t[len[i]];
for (int i = 1; i <= len[las]; ++i) t[i] += t[i - 1];
for (int i = 1; i <= sz; ++i) id[t[len[i]]--] = i;
for (int i = sz; i; --i) cnt[fa[id[i]]] += cnt[id[i]];
}
Marisa operator%(vector<Data> &a) {
int p = 1;
for (Data o: a) if (!ch[p].count(o)) return 0; else p = ch[p][o];
return cnt[p];
}
} sam;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= m; ++i) {
int c; cin >> c;
vector<Vec> b(c);
for (Vec &x: b) cin >> x;
for (int j = 2; j < c; ++j) a[i][0].emplace_back(calc(b[j - 2], b[j - 1], b[j]));
for (Vec &x: b) x = {x.x, -x.y};
for (int j = 2; j < c; ++j) a[i][1].emplace_back(calc(b[j - 2], b[j - 1], b[j]));
}
vector<Vec> b(n);
for (Vec &x: b) cin >> x;
for (int j = 2; j < n; ++j) sam += calc(b[j - 2], b[j - 1], b[j]);
~sam;
for (int i = 1; i <= m; ++i) cout << (a[i][0].empty() ? n - 1 : a[i][0] == a[i][1] ? sam % a[i][0] : sam % a[i][0] + sam % a[i][1]) << '\n';
return 0;
}