2018牛客多校第4场——Ternary string(欧拉降幂)


题意:

给你一个三进制串,每一秒每个2后面会多出一个1,每个1后会多出一个0,同时串首的字符被抹去,问多少秒后这个串会被完全消去。


思路:

赛场上不会欧拉降幂,下来补的。
首先,这种题肯定是有规律的。对于这道题,很容易想到,一个数以及他衍生出来的数要全部消去一定和他存在的时间有关,那么暴力模拟一下,跑一下01,001,0001……,02,002,0002……这些数据,就会发现规律。
如果用t表示t秒后当前字符前面的字符全部消去,那么规律如下:
如果当前字符为‘1’:T=2*t+2.
如果当前字符为’2’:T=3*(t+1) -3.
得到这个规律后,我们发现,由于t的规模很大,我们没法在不对指数t取模的情况下计算,那么像个办法对t取模,这时就要用到欧拉降幂公式:
这里写图片描述
这个公式我们可以一直迭代下去,大概迭代log(phi(c))次,phi(c)就会等于1,任何数模1都为0,再往后迭代就没有意义了,由此,这个公式不但可以解决了对指数取模的问题,还减小了问题的规模。


AC代码分析:

补题的时候,因为看不懂标程,就去网上找了大佬写的博客,大佬是用递归写的,代码很简单,也很容易懂,很开心的模改一份,提交,喜滋滋。而且跑的很快,200ms的样子,标程跑了2600ms。
于是去抓了几道欧拉降幂的题准备练练手,但怎么都过不了,后来发现递归时公式用的有问题,见代码。

#include 
#include 
#include 
#include 
#include 

using namespace std;
typedef long long ll;
const ll MOD = 1e9 + 7;
const ll INF = 1e5;
const ll maxn = 1e5 + 10;
char str[maxn];
ll ans, len;
mapmod;
ll euler(ll x) {
    ll res = x;
    for(int i = 2; i * i <= x; i++) {
        if(x % i == 0) {
            res = res / i * (i - 1);
            while (x % i == 0)
                x /= i;
        }
    }
    if(x > 1)
        res = res / x * (x - 1);
    return  res;
}

ll k_pow(ll x, ll y, ll m) {
    ll res = 1;
    while (y) {
        if (y & 1) {
            res *= x;
            res %= m;
        }
        x = x * x % m;
        y >>= 1;
    }
    return res;
}

void init(ll m) {
    while (m != 1)
        m = mod[m] = euler(m);
    mod[1] = 1;
}

ll dfs(ll pos, ll m) {
    if(pos == -1) return 0;
    else if(str[pos] == '0') return (dfs(pos - 1, m) + 1) % m;
    else if(str[pos] == '1') return (2 * (dfs(pos - 1, m) + 1)) % m;
    else return (3 * k_pow(2, dfs(pos - 1, mod[m]) + 1, m) - 3 + m) % m;//就是这里,显然这里是调用了欧拉降幂公式的,但是却没有加上phi,如果在这里加上phi就会错
}
int main() {
    int t;
    scanf("%d", &t);
    init(MOD);
    while (t--) {
        ans = 0;
        scanf("%s", str);
        len = strlen(str);
        ll ans = dfs(len - 1, MOD);
        printf("%lld\n", ans);
    }
    return 0;
}

欧拉公式使用时,有严格的限制,就是指数B>=phi(C),这份递归代码显然没有考虑这个问题,并且公式都使用错误,竟然AC了,网上的博客大同小异,询问了几个博主也没有给出正确解释。
无奈又重新读了标程,发现标程是用类似DP的方法写的,但还是没完全看懂orz,但是差不多有了思路,于是自己手写了一份自认为是正解的程序,思路见注解。

#include 
#include 
#include 

using namespace std;
typedef long long ll;
const ll maxn = 1e5 + 10;
const ll S = 262144 * 3;
const int mod[29] = {
        1000000007, 1000000006, 500000002, 243900800, 79872000,
        19660800, 5242880, 2097152, 1048576, 524288,
        262144, 131072, 65536, 32768, 16384,
        8192, 4096, 2048, 1024, 512,
        256, 128, 64, 32, 16,
        8, 4, 2, 1
};//复制标程的数组,预处理每一个phi节省时间,其实这道题时间卡的很紧
char str[maxn];
int vis[30], p2[29][S];//vis标记当前答案是否用过欧拉公式,p2同标程预处理一些2的次方的结果,不然即使快速幂也会T,而且p2不能开LL,会炸内存orz
ll ans[30];//ans[i]表示在%mod[i]意义下的答案

ll k_pow(ll x, ll y, ll m) {
    ll res = 1;
    while (y) {
        if (y & 1) {
            res *= x;
            res %= m;
        }
        y >>= 1;
        x = x * x % m;
    }
    return res;
}

int main() {
    int tcase;
    for (int i = 0; i < 28; ++i) {
        p2[i][0] = 1;
        for (int j = 1; j < S; ++j) {
            p2[i][j] = p2[i][j - 1] * 2;
            if (p2[i][j] >= mod[i]) p2[i][j] -= mod[i];//这里很巧妙,节省了1个%的时间,疯狂节省时间
        }
    }
    scanf("%d", &tcase);
    while (tcase--) {
        for (int i = 0; i < 30; i++)
            vis[i] = 0;
        scanf("%s", str);
        int len = strlen(str);
        for (int i = 0; i < len; i++) {
            if (str[i] == '0') {//字符为0或1的时候直接正常求就行,当ans大于mod的时候标记
                for (int j = 0; j < 28; j++) {
                    ans[j]++;
                    if (vis[j])
                        ans[j] %= mod[j];
                    else {
                        if (ans[j] >= mod[j + 1]) {
                            vis[j] = 1;
                            ans[j] %= mod[j];
                        }
                    }
                }
            } else if (str[i] == '1') {
                for (int j = 0; j < 28; j++) {
                    ans[j] = ans[j] * 2 + 2;
                    if (vis[j])
                        ans[j] %= mod[j];
                    else {
                        if (ans[j] >= mod[j + 1]) {
                            vis[j] = 1;
                            ans[j] %= mod[j];
                        }
                    }
                }
            } else if (str[i] == '2') {//求2的时候比较复杂,当vis[i]=1,说明当前层使用了公式,那么这个t应该是%mod[j+1]意义下的,同时要注意利用p2数组去节省时间
                for (int j = 0; j < 28; j++) {
                    if (vis[j]) {
                        if(ans[j + 1] + 1 + mod[j + 1] < S)
                            ans[j] = (3ll * p2[j][ans[j + 1] + 1 + mod[j + 1]] - 3 + mod[j]) % mod[j];
                        else
                            ans[j] = (3ll * k_pow(2, ans[j + 1] + 1 + mod[j + 1], mod[j]) - 3 + mod[j]) % mod[j];
                    }
                    else {
                        if (ans[j] > 29 - i) {//如果vis[j]=0,此时应该用ans[j]当作t,但这个2^t可能会炸ll,所以如果2^t>=mod[j]了,直接标记取模
                            if(ans[j] + 1 < S)
                                ans[j] = (3ll * p2[j][ans[j] + 1] - 3 + mod[j]) % mod[j];
                            else
                                ans[j] = (3ll * k_pow(2, ans[j] + 1, mod[j]) - 3 + mod[j]) % mod[j];
                            vis[j] = 1;
                        } else
                            if(ans[j] + 1 < S)
                                ans[j] = 3ll * p2[j][ans[j] + 1] - 3;
                            else
                                ans[j] = 3ll * k_pow(2, ans[j] + 1, mod[j]) - 3;
                        if (ans[j] <= 0) {//注意ans[j]为0的情况
                            ans[j] %= mod[j];
                            ans[j] = (ans[j] + mod[j]) % mod[j];
                            vis[j] = 1;
                        }
                    }

                }
            }
        }
        printf("%lld\n", ans[0]);
        for (int i = 0; i <= 28; i++)
            ans[i] = 0;
    }
    return 0;
}

你可能感兴趣的:(数论,欧拉降幂)