[ZJOI2008] 无序运动 题解

你谷 link

一道蛮模版的字符串题,只需要一步非常显然的转化。

首先思考如果无视四种操作该怎么做,如果将每一个点看成是一个字符,那么就是给定一个文本串,多个模式串,求每个模式串在文本串里出现几次,这显然是一个字符串的模板题,可以使用 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;
}
posted @ 2022-07-20 08:46  老莽莽穿一切  阅读(90)  评论(0编辑  收藏  举报