【ybt金牌导航8-5-1】【luogu P4980】彩色项链1 / 【模板】Pólya 定理

彩色项链1

题目链接:ybt金牌导航8-5-1 / luogu P4980

题目大意

一个项链,然后有 n 个颜色让你涂在 n 个珠子上。
(不一定要用完所有颜色)
然后问你有多少种项链本质不同。

本质不同要两个项链无论怎么选择都不能重合(即对于位置颜色相同)。

思路

我们看到本质不同,然后它又是把一些状态转移,自然会想到 Burnside 引理和 Polya 定理。

它是干嘛的呢?
就是针对这种状态可以转移的东西,然后能转移就表示本质相同,它就是用来求有多少本质不同的状态。

然后能使得状态转移的东西,就叫做置换。
f=(a1,a2,...,an) 这个置换,表示 i 可以转移到 ai

然后你会知道按着这么搞,会出现一个或多个循环,就是某个点可以一直在这个循环里面的跳。
那这些循环之间是不相交的。

Burnside 引理就是可以解决有多个置换时能有的本质不同状态个数。
这里先给出 Burnside 引理的公式:
L=1|G|rGc1(r)

G 就是置换群,包含了给出的所有置换。c1(r) 就是在置换 r 中自己直接转移到自己的状态个数。

至于这个证明,我太菜了,证不了。用就完事了!

接着来看 Polya 定理,就是这道题要用的算法。
它就是可以处理什么多种颜色的情况。

在前面的引理中,你需要列出所有的状态,但是如果颜色一多,你就不能表示出所有状态,就需要用这个算法了。

公式是这个:
L=1|G|rGmc(r)

这里 c(r) 的意义不再一样,就是 r 置换中循环的个数。
它主要就是说每个循环都可以选任意颜色,然后循环中的点的颜色是相同的,然后中间就变成了这样。

m 是颜色的个数。

然后你列出置换 rk(k+1,k+2,...,n,1,2,...,k)
可以知道 c(r)=gcd(k,n)|G|=n

但是还是不是很优,我们考虑继续优化。
L=1nd|n(nd[d=gcd(k,n)])
(因为是 gcd,所以我们可以把 gcd 同一个值的提取出来,前面的求和是单个的贡献,里面的是求出有多少个同一个值的)
L=d|n(nd1[1=gcd(kd,nd)])
那你会发现 gcd 的右边是跟里面的循环没有关系,而且是要 gcd 里面的两个互质,自然想到欧拉函数。
L=d|n(nd1φ(nd))

然后就可以做了。

代码

ybt

#include<cstdio> #define ll long long using namespace std; int T, n; ll mo, ans, su[35001]; bool ns[35001]; void get_su() {//欧拉筛筛出素数 ns[1] = ns[0] = 1; for (int i = 2; i <= 35000; i++) { if (!ns[i]) { su[++su[0]] = i; } for (int j = 1; j <= su[0] && i * su[j] <= 35000; j++) { ns[i * su[j]] = 1; if (i % su[j] == 0) break; } } } ll ksm(ll x, ll y) {//快速幂 ll re = 1; while (y) { if (y & 1) re = (re * x) % mo; x = (x * x) % mo; y >>= 1; } return re; } ll phi(ll now) {//算phi值 ll re = now; for (ll i = 1; su[i] * su[i] <= now; i++) if (now % su[i] == 0) { re = re / su[i] * (su[i] - 1); while (now % su[i] == 0) now /= su[i]; } if (now != 1) re = re / now * (now - 1); return re; } int main() { get_su(); scanf("%d", &T); for (int times = 1; times <= T; times++) { scanf("%d %lld", &n, &mo); ans = 0; for (int i = 1; i * i <= n; i++) {//枚举因子 if (n % i == 0) { //我们上面弄出来的公式 ans = (ans + ksm(n, i - 1) * phi(n / i) % mo) % mo; if (i * i != n) ans = (ans + ksm(n, (n / i) - 1) * phi(n / (n / i)) % mo) % mo; } } printf("%lld\n", ans); } return 0; }

luogu

#include<cstdio> #define ll long long #define mo 1000000007 using namespace std; int T, n; ll ans, su[35001]; bool ns[35001]; void get_su() { ns[1] = ns[0] = 1; for (int i = 2; i <= 35000; i++) { if (!ns[i]) { su[++su[0]] = i; } for (int j = 1; j <= su[0] && i * su[j] <= 35000; j++) { ns[i * su[j]] = 1; if (i % su[j] == 0) break; } } } ll ksm(ll x, ll y) { ll re = 1; while (y) { if (y & 1) re = (re * x) % mo; x = (x * x) % mo; y >>= 1; } return re; } ll phi(ll now) { ll re = now; for (ll i = 1; su[i] * su[i] <= now; i++) if (now % su[i] == 0) { re = re / su[i] * (su[i] - 1); while (now % su[i] == 0) now /= su[i]; } if (now != 1) re = re / now * (now - 1); return re; } int main() { get_su(); scanf("%d", &T); for (int times = 1; times <= T; times++) { scanf("%d", &n); ans = 0; for (int i = 1; i * i <= n; i++) { if (n % i == 0) { ans = (ans + ksm(n, i - 1) * phi(n / i) % mo) % mo; if (i * i != n) ans = (ans + ksm(n, (n / i) - 1) * phi(n / (n / i)) % mo) % mo; } } printf("%lld\n", ans); } return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_8-5-1.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(43)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示