【XP的胡策】互测3.20(prufer数列+dp+组合数学+扩展欧拉定理)

本文介绍了如何使用Prufer数列和动态规划(dp)解决有度数限制的无根树计数问题,并探讨了Prufer数列的生成规则、性质及其与有标号无根树的对应关系。此外,文章还讨论了在模运算下,如何应用扩展欧拉定理优化计算,以解决指数问题,降低时间复杂度。
摘要由CSDN通过智能技术生成

T1:

这里写图片描述
这里写图片描述

题意:

一句话题意:有度数限制情况下求有标号无根树数量

题解:

一眼dp,怎么dp呢?
我们先抛开度数限制考虑一下
关于有标号无根树的计数问题,有一个非常经典的模型,就是把树转化成prufer数列

prufer数列普及:

Prufer数列的生成规则:

找出这棵树的当前叶子节点中标号最小的那个,把与它相连的点的编号加入prufer数列,然后删除这个叶子节点。
操作到最后剩下两个节点时,这两个节点会被同时删除。

Prufer数列可以通过如下方式还原成原先的树:

每次找到当前prufer数列中未出现的编号中最小的编号,把它对应的点和当前prufer数列中开头的点连接,然后删除数列开头的点。

prufer数列的性质:

1、一个n个节点的有标号无根树的prufer数列长度为n-2
证明:一个树删删删最后肯定只剩两个节点,所以除了这两个节点都会带来一个节点的贡献
2、原树中节点度数为di的节点编号在prufer数列中出现di-1次
证明:由于一个节点在作为叶子结点删除时,他的当前度数为1,而每删除一个与他相邻的叶子结点时,他的编号都会被计入一次,且他的度数-1,所以最终会被记录di-1次,然后作为叶子结点被删除
3、每个prufer数列都和一棵有标号无根树一一对应。
证明:由于在对有标号无根树进行操作时,每一步的操作都是固定的(选取最小标号叶子节点),所以每棵无根树只对应唯一一个prufer数列。由于prufer数列在还原成树时每步操作也是唯一的,所以每个prufer数列只能还原成唯一的树。

既然prufer数列和有标号无根树一一对应,那么我们就可以把有标号无根树计数转换为prufer数列计数
可以dp了?dp[i][j][k]表示前i个点,选了j个,当前的prufer数列长度为k时的方案数
如果当前的点不选,dp[i][j][k]=dp[i-1][j][k]
如果选了这个点,就需要枚举出现次数
考虑度数限制,每个数在prufer数列中不能出现超过ai-1次
dp[i][j][k]=ai1d=0dp[i1][j1][kd](k>=d) d p [ i ] [ j ] [ k ] = ∑ d = 0 a i − 1 d p [ i − 1 ] [ j − 1 ] [ k − d ] ( k >= d )

知道了每个数在prufer数列中出现的次数,那prufer数列的个数也好求了
设li为第i个数字在prufer数列中出现的次数,则s=p时的答案就是
dp[n][p][p2](p2)!pi=1li! d p [ n ] [ p ] [ p − 2 ] ∗ ( p − 2 ) ! ∏ i = 1 p l i !
(排列组合:每个字母个数固定有多少不一样的字符串)
这个分子我们预处理就行了,但是分母很不好了,我们可以在转移的之后带上分母一起转移
dp[i][j][k]=ai1d=0dp[i1][j1][kd]d!(k>=d) d p [ i ] [ j ] [ k ] = ∑ d = 0 a i − 1 d p [ i − 1 ] [ j − 1 ] [ k − d ] d ! ( k >= d )

那么最后的答案就是dp[n][p][p-2]*(p-2)!
然后因为模数是个质数,可以预处理阶乘逆元,时间复杂度 O(n4) O ( n 4 )
但是因为有边界啊啥的对于n<=100是可以跑过去的

代码:

#include <cstdio>
#define LL long long
using namespace std;
const int mod=19260817;
const int N=105;
int a[N],n,dp[N][N][N],mul[N],ny[N];
LL ksm(LL a,LL k)
{
    LL ans=1;
    for (;k;k>>=1,a=a*a%mod)
      if (k&1) ans=ans*a%mod;
    return ans;
}
void init()
{
    mul[0]=ny[0]=1;
    for (int i=1;i<=n;i++) mul[i]=(LL)mul[i-1]*i%mod;
    for (int i=1;i<=n;i++) ny[i]=ksm(mul[i],mod-2);
}
int main()
{
    freopen("crystal.in","r",stdin);
    freopen("crystal.out","w",stdout);
    scanf("%d",&n);
    for (int i=0;i<n;i++) scanf("%d",&a[i]);
    init();
    dp[0][0][0]=1;
    for (int i=0;i<n;i++)
      for (int j=0;j<n;j++)
        for (int k=0;k<n;k++)
        if (dp[i][j][k])
        {
            dp[i+1][j][k]=(dp[i+1][j][k]+dp[i][j][k])%mod;
            for (int d=0;d<=a[i]-1 && k+d<=n-2;d++)
              dp[i+1][j+1][k+d]=(dp[i+1][j+1][k+d]+(LL)dp[i][j][k]*ny[d]%mod)%mod;
        }
    printf("%d",n);
    for (int i=2;i<=n;i++) 
      printf(" %lld",(LL)dp[n][i][i-2]*mul[i-2]%mod);
}

T2:

我是超链接
这里写图片描述

题解:

这个指数肯定不能放任不管不取膜,但是膜什么呢?先吐槽一波lj出题人让%p的都过了
欧拉定理有言曰: aφ(p)1(%p) a φ ( p ) ≡ 1 ( % p )
那么我们对于l~r的数字套上φ(p)膜过去
还有一个问题,欧拉定理只有在a,p互质的情况下才有用,其实有一种东西叫做扩展欧拉定理(当时我懵懂无知,见到了却不知道是啥也没去学的东西。)
abab%φ(p)+φ(p)(%p) a b ≡ a b % φ ( p ) + φ ( p ) ( % p )
这样我们只需要特殊处理一下%的操作就可以了
还有一件事,phi的取值不过logn就会变成1,而任何数%1=0,所以我们并不用赋值到最后,只需要到1为止就行了,这样时间复杂度就是 O(qlog2n) O ( q l o g 2 n )
注意求phi的时候要开longlong。。。

代码:

#include <cstdio>
#define LL long long
using namespace std;
const int N=100005;
int mod,p,n,a[N],ans[N],xs[N],ph[N];
LL mode(LL a,LL b)
{
    if(a<b) return a;
    else return a%b+b;
}
LL ksm(LL a,LL k,int mod)
{
    LL ans=1;
    for (;k;k>>=1,a=mode(a*a,mod))
      if (k&1) ans=mode(ans*a,mod);
    return ans;
}
int phi(LL x)
{
    LL ans=x; 
    for (int i=2;i*i<=x;i++)
      if (x%i==0)
      {
        ans=ans*(i-1)/i;
        while (x%i==0) x/=i;
      }
    if (x>1) ans=ans*(x-1)/x;
    return ans;
}
int main()
{
    freopen("battery.in","r",stdin);
    freopen("battery.out","w",stdout);
    scanf("%d%d",&n,&p);
    int q,l,r;
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    ph[1]=p;int md=1;while (ph[md]!=1) ph[++md]=phi(ph[md-1]);
    scanf("%d",&q);
    while (q--)
    {
        scanf("%d%d",&l,&r);
        int pos=r;
        for (int i=l,lj=1;i<=r;i++,lj++) 
        {
            xs[i]=ph[lj];
            if (xs[i]==1) {pos=i;break;}
        }
        int last=mode(a[pos],xs[pos]);
        for (int i=pos-1;i>=l;i--)
          last=ksm(a[i],last,xs[i]);
        printf("%d\n",last%p);
    }           
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值