给你一个三进制串,每一秒每个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,再往后迭代就没有意义了,由此,这个公式不但可以解决了对指数取模的问题,还减小了问题的规模。
补题的时候,因为看不懂标程,就去网上找了大佬写的博客,大佬是用递归写的,代码很简单,也很容易懂,很开心的模改一份,提交,喜滋滋。而且跑的很快,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;
map mod;
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;
}