nowcoder20072 [HNOI2009]图的同构

链接

点击跳转

题解

每条边能可以连、可以不连,其实就可以看作完全图每条边可以涂蓝色可以涂红色

那么这个题又变成了等价类计数问题

枚举边的置换的话,是不对的,因为直接枚举边的置换会导致本来有公共端点的边置换之后失去了公共顶点

直接枚举点的置换,就可以得到一个边的置换,而且是合法的所有边的合法置换都可以对应到一个点的置换

好,现在我枚举了一个点的置换

现在我们不是要使用 b u r n s i d e burnside burnside吗,那我就要求出一个点的置换对应的边置换的等价类数目(假设是m),然后把 2 m 2^m 2m加入答案。

怎么求呢?

边有两种,第一种是两个顶点都在同一个环内(我说的是置换里的环),可以发现对于一个大小为 s s s的环,这种等价类共有 ⌊ s 2 ⌋ \lfloor \frac{s}{2} \rfloor 2s个(其实很好数,间隔为 1 1 1的、间隔为 2 2 2的、间隔为 3 3 3的…间隔为 ⌊ s 2 ⌋ \lfloor \frac{s}{2} \rfloor 2s的);然后再就两个端点分属两个环的边,假设第一个环大小为 a a a,另一个大小为 b b b,那么我先固定边的一头在第一个环的某个点,另一头在第二个环的某个点,然后开始“转”,每次每个换上的点“转”到下一个点,最后肯定会转回来,并且一共经过了 l c m ( a , b ) lcm(a,b) lcm(a,b)个点,这是显而易见的,那么一共有 a b ab ab条边,我可以得到等价类的数目为 a b l c m ( a , b ) = g c d ( a , b ) \frac{ab}{lcm(a,b)} = gcd(a,b) lcm(a,b)ab=gcd(a,b)

对所有的置换的 2 m 2^m 2m求和( m m m代表等价类数目),然后除以 n ! n! n!就是答案

但是我肯定不能枚举置换啊,那怎么办?其实很简单,枚举整数拆分,算出 m m m,然后再用组合数学的知识算出这种拆分对应了多少种置换即可

这样的话好像最大数据还是要跑好几秒,但是一看 N N N那么小,我直接打个表交上去不就完了

代码

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#define iinf 0x3f3f3f3f
#define linf (1ll<<60)
#define eps 1e-8
#define maxn 300010
#define cl(x) memset(x,0,sizeof(x))
#define rep(i,a,b) for(i=a;i<=b;i++)
#define em(x) emplace(x)
#define emb(x) emplace_back(x)
#define emf(x) emplace_front(x)
#define fi first
#define se second
#define de(x) cerr<<#x<<" = "<<x<<endl
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
ll read(ll x=0)
{
    ll c, f(1);
    for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-f;
    for(;isdigit(c);c=getchar())x=x*10+c-0x30;
    return f*x;
}
#define mod 997ll
ll fact[maxn], _fact[maxn], pow2[maxn], inv[maxn], ans=0, N, v[maxn], tot;
void dfs(ll res, ll mn)
{
    ll i, j;
    if(res==0)
    {
        ll cnt = fact[N], sum=0, t=0;
        // rep(i,1,tot)printf("%lld ",v[i]); putchar(10);
        rep(i,1,tot)(cnt*=inv[v[i]])%=mod;
        rep(i,1,tot)
        {
            if(v[i]==v[i-1])t++;
            else
            {
                (cnt*=_fact[t])%=mod;
                t=1;
            }
        }
        (cnt*=_fact[t])%=mod;
        rep(i,1,tot)sum+=v[i]/2;
        rep(i,1,tot)rep(j,i+1,tot)sum+=__gcd(v[i],v[j]);
        (ans+=cnt*pow2[sum])%=mod;
    }
    rep(i,mn,res)v[++tot]=i, dfs(res-i,i), tot--;
}
int main()
{
    ll i;
    inv[1]=1;for(int i=2;i<maxn;i++)inv[i]=inv[mod%i]*(mod-mod/i)%mod;
    pow2[0]=fact[0]=_fact[0]=1;
    rep(i,1,maxn-1)fact[i]=fact[i-1]*i%mod, _fact[i]=_fact[i-1]*inv[i]%mod, pow2[i]=pow2[i-1]*2%mod;
    // N=read();
    // dfs(N,1);
    // (ans*=_fact[N])%=mod;
    // printf("%lld",ans);
    vector<int> lis({1, 2, 4, 11, 34, 156, 47, 382, 493, 291, 56, 400, 993, 778, 96, 890, 888, 766, 749, 7, 304, 785, 887, 46, 799, 403, 68, 742, 852, 567, 582, 803, 231, 122, 61, 761, 151, 931, 617, 870, 170, 736, 521, 412, 976, 217, 383, 119, 447, 314, 793, 952, 321, 665, 663, 780, 791, 78, 403, 683});
    cout<<lis[read()-1]<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值