计算几何和哈希能组合出什么?OIer竟惊叹连连!Luogu P3454 [POI2007]OSI-Axes of Symmetry 题解
[POI2007]OSI-Axes of Symmetry
题目
链接:P3454 [POI2007]OSI-Axes of Symmetry
大意:
给定 个点,点全部按顺时针或逆时针的顺序给出。求这 个点围成的多边型的对称轴的数量。
思路:
点既然按顺序给出,就可以依次记录多边形的角和边,连在一起就得到了一个长为 的环,最后一项连接着第一项。
我们把样例那个六边形拿出来:
角度是实数,不太好维护,所以可以用计算几何的一个小方法,叉积。
众所周知, 。所以叉积可以代替角。
同样的,边长可以用它的平方代替,免去开方。
这样,一个实数环就可以转化成整数环。
上图,环就可以变成:
于是乎,一个看似是计算几何的问题就可以转化为:环上有几个位置,满足将环从这里断开后,可以得到一条回文链。
同时,一条对称轴穿过两个位置,最后的答案就是满足的位置的个数除以二。
so,这就结束了吗?
我们来看这样一组数据:
input: 1 4 0 0 1 1 2 1 1 0 output: 0
对应的图:
如果我们只用 来判断角会错,这里 , ,然而 ,所以,序列会变成:
所以,会错误的判断出有四个位置,两条对称轴。
既然锅出在角上,能不能再维护一个序列?
叉积依靠 值表示角,那我们可以再维护一个 值。
显然,我们可以想到点积,
。
再利用点积维护一个序列,hack数据的点积序列为:
样例六边形的点积序列为:
同上边的叉积序列比较,不难得出,当一个位置能同时把叉积序列和点积序列都断成回文串时,这个位置才满足要求。
之后就是处理环了,常见的方法是破环为链,然后把序列复制一份到后面。
对于每个 ,检查区间 是否是回文串(断环成链),此处用哈希求解。
Code:
#include<cstdio> #include<algorithm> #define ULL unsigned long long using namespace std; const int MAXN = 100010; const int Base = 233; int t, n, ans; long long s1[MAXN << 2]/*叉乘序列*/, s2[MAXN << 2]/*点乘序列*/; ULL power[MAXN << 2], hash_l_1[MAXN << 2], hash_r_1[MAXN << 2]; ULL hash_l_2[MAXN << 2], hash_r_2[MAXN << 2]; struct Dikayer{ int x, y; }p[MAXN]; inline int read(){ int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } long long Get_Cross(Dikayer a, Dikayer b, Dikayer c){ return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y); } //求 ab 叉乘 ac long long Get_Point(Dikayer a, Dikayer b, Dikayer c){ return (b.x - a.x) * (c.x - a.x) + (b.y - a.y) * (c.y - a.y); } //求 ab 点乘 ac long long Get_Dis(Dikayer a, Dikayer b){ return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); } //求 ab 的长度 bool Check(int l, int r){ //正反的哈希值一样,即为回文串 ULL res_l_1 = hash_l_1[r] - hash_l_1[l - 1] * power[r - l + 1]; ULL res_r_1 = hash_r_1[l] - hash_r_1[r + 1] * power[r - l + 1]; ULL res_l_2 = hash_l_2[r] - hash_l_2[l - 1] * power[r - l + 1]; ULL res_r_2 = hash_r_2[l] - hash_r_2[r + 1] * power[r - l + 1]; if(res_l_1 == res_r_1 && res_l_2 == res_r_2) return true; //叉乘序列和点乘序列都为回文串 else return false; } void init(){ power[0] = 1; for(register int i = 1; i < (MAXN << 2); i++) power[i] = power[i - 1] * Base; } void Get_Hash(long long *s, ULL *hash_l, ULL *hash_r){ for(register int i = 1; i <= (n << 2); i++) hash_l[i] = hash_l[i - 1] * Base + s[i]; for(register int i = (n << 2); i >= 1; i--) hash_r[i] = hash_r[i + 1] * Base + s[i]; } int main(){ init(); t = read(); while(t--){ ans = 0; n = read(); for(register int i = 1; i <= n; i++) p[i].x = read(), p[i].y = read(); for(register int i = 1; i <= n; i++){ int pos1 = i, pos2 = i + 1, pos3 = i + 2; pos2 > n ? pos2 -= n : pos2 += 0; pos3 > n ? pos3 -= n : pos3 += 0; int edge = Get_Dis(p[pos1], p[pos2]); //边 int angle_sin = Get_Cross(p[pos2], p[pos1], p[pos3]); //角sin int angle_cos = Get_Point(p[pos2], p[pos1], p[pos3]);//角cos s1[(i << 1) - 1] = edge; s1[i << 1] = angle_sin; s2[(i << 1) - 1] = edge; s2[i << 1] = angle_cos; } for(register int i = 1; i <= (n << 1); i++){ s1[i + (n << 1)] = s1[i]; s2[i + (n << 1)] = s2[i]; } //复制一遍 Get_Hash(s1, hash_l_1, hash_r_1); Get_Hash(s2, hash_l_2, hash_r_2); for(register int i = 1; i <= (n << 1); i++) if(Check(i, i + (n << 1))) ans++; printf("%d\n", ans / 2); } return 0; }
以下为博客签名,与博文无关。
只要你们不停下来,那前面就一定有我。所以啊,不要停下来~
本文来自博客园,作者:TSTYFST,转载请注明原文链接:https://www.cnblogs.com/TSTYFST/p/16530316.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理