zoukankan      html  css  js  c++  java
  • 【HDU 6021】 MG loves string (枚举+容斥原理)

    MG loves string

     Accepts: 30
     Submissions: 67
     Time Limit: 2000/1000 MS (Java/Others)
     Memory Limit: 262144/262144 K (Java/Others)
    问题描述
    MG是一个很忙碌的男孩子。今天他沉迷于这样一个问题:
    
    对于一个长度为N的由小写英文字母构成的随机字符串,当它进行一次变换,所有字符i都会变成a[i]MG规定所有a[i]构成了26个字母组成的排列。
    
    MG现在需要知道这个随机串变换到自身的期望变换次数。请你输出期望答案乘上26^n以后模 1000000007的结果。
    
    MG认为这件事非常容易,不屑于用计算机解决,于是运用他高超的人类智慧开始进行计算。作为一名旁观者,你也想挑战MG智慧,请你写个程序,计算答案。
    
    输入描述
    第一行一个整数T,代表数据组数(1 <=T<=10)。
    
    接下来,对于每组数据——
    
    第一行一个整数N,表示给定的随机串长度(1<=N<=1000000000)。
    
    第二行26个字母,表示a_i​​序列
    
    输出描述
    对于每一组数据,输出一行。
    
    显然,这个期望是一个实数。请你输出它乘上26^N​​以后模 1000000007 的结果
    
    输入样例
    2
    2
    abcdefghijklmnpqrstuvwxyzo
    1
    abcdefghijklmnopqrstuvwxyz
    输出样例
    5956
    26


     

    【分析】

      感觉BC的题挺好的啊【每次都能学到东西。。

      首先,知道,这是个带LCM的期望。就是看随机串分别在长度为几的循环节里面,然后LCM。

      然后,不同长度的循环节不会超过6个,1+2+3+4+5+6=21。

      就是根据输入的那个串,只会有6种长度的循环节,所以你可以枚举真正的随机串涵盖的循环节有哪几个,枚举是2^6。

      然后就是把n个字符放到那些循环节的字母集合中去,但是要保证每个循环节都一定有一个字母覆盖,问它的方案数。

      其实这是经典的容斥原理,就是n个东西分到m个集合,让每个集合都至少有一个东西。

      这里我们枚举子集就可以用容斥原理计算出来了【注意容斥,你要减掉的是没有涵盖某一个集合的,加上没有涵盖两个集合的。。。】

      枚举子集是3^n(用二项式定理易证)

      这个可以预处理的。

      所以是$O(2^6*log(n)+3^6)$

    官方题解:

     1 #include<cstdio>
     2 #include<cstdlib>
     3 #include<cstring>
     4 #include<iostream>
     5 #include<algorithm>
     6 using namespace std;
     7 #define Mod 1000000007
     8 #define LL long long
     9 
    10 int u[30],v[10],pp[10],n;
    11 LL sm[1010];
    12 int h[1010],ss[1010],p[30];
    13 char s[30];
    14 bool vis[30];
    15 
    16 LL qpow(LL x,int b)
    17 {
    18     x%=Mod;
    19     LL ans=1;
    20     while(b)
    21     {
    22         if(b&1) ans=(ans*x)%Mod;
    23         x=(x*x)%Mod;
    24         b>>=1;
    25     }
    26     return ans;
    27 }
    28 
    29 void init()
    30 {
    31     memset(vis,0,sizeof(vis));
    32     memset(p,0,sizeof(p));
    33     memset(h,0,sizeof(h));
    34     memset(ss,0,sizeof(ss));
    35     scanf("%d",&n);
    36     scanf("%s",s+1);
    37     for(int i=1;i<=26;i++) u[i]=s[i]-'a'+1;
    38     for(int i=1;i<=26;i++) if(!vis[i])
    39     {
    40         vis[i]=1;
    41         int x=i,cnt=1;
    42         while(u[x]!=i) x=u[x],cnt++,vis[x]=1;
    43         p[cnt]++;
    44     }
    45     v[0]=0;
    46     for(int i=1;i<=26;i++) if(p[i]) v[++v[0]]=i,pp[v[0]]=p[i];
    47     for(int i=0;i<(1<<v[0]);i++)
    48      for(int j=1;j<=6;j++) if(i&(1<<j-1)) ss[i]+=v[j]*pp[j],h[i]++;
    49     for(int i=0;i<(1<<v[0]);i++) sm[i]=qpow(ss[i],n);sm[0]=0;
    50     int i;
    51     // for(i=0;i<(1<<v[0]);i++)
    52     for(i=(1<<v[0])-1;i>=0;i--)
    53      for(int j=i;j;j=(j-1)&i)
    54      {
    55          if(i==j) continue;
    56          if((h[i]-h[j])%2==0) sm[i]+=sm[j];
    57          else sm[i]-=sm[j];
    58          sm[i]=(sm[i]%Mod+Mod)%Mod;
    59      }
    60 }
    61 
    62 LL gcd(LL a,LL b)
    63 {
    64     if(b==0) return a;
    65     return gcd(b,a%b);
    66 }
    67 
    68 LL ans;
    69 
    70 void ffind(int x,int y,LL nw)
    71 {
    72     if(x==v[0]+1)
    73     {
    74         ans=(ans+sm[y]*nw)%Mod;
    75         return;
    76     }
    77     ffind(x+1,y,nw);
    78     ffind(x+1,y|(1<<x-1),nw*(LL)v[x]/gcd(nw,v[x]));
    79 }
    80 
    81 int main()
    82 {
    83     int T;
    84     scanf("%d",&T);
    85     while(T--)
    86     {
    87         init();
    88         ans=0;ffind(1,0,1);
    89         printf("%lld
    ",ans);
    90     }
    91     return 0;
    92 }
    View Code

    2017-04-02 10:41:18

  • 相关阅读:
    java 求 1!+2!+3!+....+10!的和为
    Java 循环控制语句
    java for 循环 九九乘法表
    Java for 循环
    Java while 和 do...while
    Java if语句
    Java switch 语句
    java a++ 和 ++a 理解
    Java 自动转换和强制转换
    二叉树遍历
  • 原文地址:https://www.cnblogs.com/Konjakmoyu/p/6658345.html
Copyright © 2011-2022 走看看