• hdu 6021 MG loves string


                                                                      MG loves string

    Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)
    Total Submission(s): 131    Accepted Submission(s): 50


    Problem Description
    MG is a busy boy. And today he's burying himself in such a problem:

    For a length of N , a random string made of lowercase letters, every time when it transforms, all the character i will turn into a[i] .

    MG states that the a[i] consists of a permutation .

    Now MG wants to know the expected steps the random string transforms to its own.

    It's obvious that the expected steps X will be a decimal number.
    You should output X26Nmod 1000000007 .
     
    Input
    The first line is an integer T which indicates the case number.(1<=T<=10 )

    And as for each case, there are 1 integer  in the first line which indicate the length of random string(1<=N<=1000000000 ).

    Then there are 26 lowercase letters a[i] in the next line.
     
    Output
    As for each case, you need to output a single line.

    It's obvious that the expected steps X will be a decimal number.
    You should output X26Nmod 1000000007 .
     
    Sample Input
    2
    2
    abcdefghijklmnpqrstuvwxyzo
    1
    abcdefghijklmnopqrstuvwxyz
     
    Sample Output
    5956
    26
     
    题意:给定26个小写字母x1,x2,...,x26的字符串作为“密码表”,26个密码分别对应a~z26个小写字母,一个字母进行一次变换,意味着该字母变换成对应的密码,譬如字母b下一次变换应该变成x2,字母x2会变换成x2对应的密码等等(可以知道,经过有限次的变换,每个字母最终还是会变换回来的)。
    现在给定一个随机字符串的长度N,并且字符串是由任意的小写字母组成,且该字符串经过有限次变换还是会变换到自身,求任意字符串的变换期望次数。
    思路:求期望次数,首先想到的是穷举每一种长度为N的小写字符串(一共26^N种可能),对于每次得到的字符串,求出变换到自身的变换次数,之后求平均。当然这样的效率是极其低下的。每个字母经过有限次变换都是可以变换到自身的,将这个过程中所有经过变换得到的字母组成一个圈,那么这个圈中每一个字母都会变换相同的次数之后变换到自身),这样一来,所有的26个字母就可以被挑拣成几个不同的圈,情况最坏的时候每个字母分别成为一个圈,一共26个圈,有点多,现在继续考虑能否将长度一样的圈挑出来成为一个group,那么这样一共会有多少group呢,最坏的情况,圈的长度从1开始变化,1+2+3+4+5+6+7=28>26,也就是说绝对不可能产生七个长度不同的group,最多6个,这么一来范围就小多了。
    现在考虑长度相同的每一个group的性质,可以知道每一个group中的每个字母变换到自身的次数都相同。
    那么现在长度为N的随机字符串的每一个位置,都可以由用每一个group中任意一个字符来填充。穷举group的组合情况,对于每一种group的组合情况,随机字符串中的每一位都由这些group中的字符来填充(因为每一个group都至少得派出一个字符来填充随机字符串,所以每一种group的组合情况中group的数量必须小于等于随机串长度N,否则直接排除在这种group的组合),那么由这些group中的字符组成随机串对期望变换次数的贡献=这些group构成的字符串的变换次数change_time*这些group构成的字符串的数量number/(26^N);
    其中,change_time=组合中每一个group长度的最小公倍。
    p=number/(26^N)则可以由容斥原理得到,举个简单的例子,设Pabc=长度为n的随机串用group a,b,c或其一或其二中字母组成的概率,显然Pabc=((size(a)+size(b)+size(c))/26)^N.那么随机串由group a,b,c组成的概率P=Pabc-Pab-Pac-Pbc+pa+pb+pc;
    穷举group的组合情况并累加贡献即可得到最终期望。
    AC代码:
    #define _CRT_SECURE_NO_DEPRECATE
    #include<iostream>
    #include<algorithm>
    #include<string>
    #include<cmath>
    #include<queue>
    #include<set>
    #include<map>
    #include<cstring>
    #include<vector>
    using namespace std;
    typedef long long ll;
    const int N_MAX = 1000000000 + 2,INF= 1000000007;
    int N;
    string s;
    vector<pair<int,int> >loop;//圈的大小<->圈的个数
    
    ll gcd(ll a,ll b) {
        if (b == 0)return a;
        return gcd(b, a%b);
    }
    
    ll lcm(ll a,ll b) {
        return a / gcd(a, b)*b;
    }
    
    void cal_loop() {
        loop.clear();
        int change[26],vis[26];
        memset(vis,0,sizeof(vis));
        for (int i = 0; i < s.size();i++) {
            change[i] = s[i] - 'a';
        }
        map<int, int>m;
        for (int i = 0; i < 26;i++) {
            
            if (vis[i])continue;
            vis[i] = true;
            int num = 1;
            int k = change[i];//k代表不断变化的字母
            while (i != k) {
                vis[k] = true;
                num++;//该圈的元素个数加1
                k = change[k];//!!!!!顺序
            }
            m[num]++;
        }
        for (map<int, int>::iterator it = m.begin(); it != m.end();it++) {
            loop.push_back(*it);
        }
    }
    
    ll mod_pow(ll x,ll n) {//快速幂运算
        ll res = 1;
        while (n>0) {
            if (n & 1)res = res*x%INF;
            x = x*x%INF;
            n >>= 1;
        }
        return res;
    }
    
    ll R_C(vector<int>&loop,int N) {//容斥原理,求由这些圈中的元素组成的长度为N的字符串的数量
        ll permute = 1 << (loop.size());
        ll ans = 0;
        for (int i = 0; i < permute;i++) {
            int num = 0;
            int sum = 0;
            int sign=-1;
            for (int j = 0; j < loop.size(); j++) {
                if (i&(1 << j)) {
                    num++;//num记录利用到的圈的个数
                    sum += loop[j];//利用到的字符的总个数
                }
            }    
                if (num % 2 == loop.size() % 2)//!!!!!!!!
                    sign = 1;
                ans =(ans+((sign*mod_pow(sum, N))%INF+INF)%INF)%INF;
        }
        return ans;
    }
    
    ll solve(int N) {
        cal_loop();
        vector<int>vec;
        ll ans = 0;
        for (int i = 0; i < (1<<loop.size());i++) {
            ll change_time=1;
            vec.clear();
            for (int j = 0; j < loop.size();j++) {
                if (i&(1 << j)) {
                    vec.push_back(loop[j].first*loop[j].second);
                    change_time = lcm(change_time, loop[j].first);
                }
            }
            if (vec.size() > N)continue;//挑选出来的圈的个数不能超过字符串长度
        ll number = R_C(vec, N);
        ans = (ans + change_time*number) % INF;
        }
        return ans;
    }
    
    
    int main() {
        int T;
        scanf("%d",&T);
        while (T--) {
            scanf("%d",&N);
            cin >> s;
            printf("%lld
    ",solve(N));
        }
        return 0;
    }

     
  • 相关阅读:
    最短路 dijkstra 优先队列
    树状数组+二分答案查询第k大的数 (团体程序设计天梯赛 L3-002. 堆栈)
    c++优先队列(堆)
    团体程序设计天梯赛 L3-012. 水果忍者
    团体程序设计天梯赛 L2-028. 秀恩爱分得快
    团体程序设计天梯赛 L1-049. 天梯赛座位分配(测试数据+不同方法)
    奇偶校验——设计可以检验错误位置的方法
    奇偶校验的错误概率
    Fibonacci数列时间复杂度之美妙
    团体程序设计天梯赛 L3-016. 二叉搜索树的结构
  • 原文地址:https://www.cnblogs.com/ZefengYao/p/6855874.html
Copyright © 2020-2023  润新知