题目描述:
给个长度为n的三进制串,有这样一个操作:在每个2后⾯面插入一个1,每个1后面插 入一个0,然后删掉第一个字符。问多少次操作后,变成空串 n <= 1e5。
题目分析:
比赛的时候一直不知道要怎么做,直到赛后才知道这题是个打表题然后用数论的知识进行解决。(emmmm,原本表就打不出来了,结果还加个数论emmmmm)于是我们就来学习一下这题的做法。
首先我们要发现(愉快的打表过程开始了),在删除字符串的过程中,如果遇到0,直接删除,操作次数+1。而如果遇到1,假设我们之前操作了x次,那么这个1后面就多产生了x个0。因此,此时我们需要再经过x+2次操作才能删完所有字符。而当遇到2,考虑之前已经操作了了x次,然后打(oe)个(is)表可以发现,之后还需要经过次操作才能全部删完 。
但是,直接快速幂取模或者用矩阵快速幂去处理上述的式子是不可行的,因为x可能相当的大,这就使得如果使用快速幂之类的求幂算法必定会超时。因此我们需要考虑数论中的一种降幂公式。具体看下图:
有了这条公式,我们就可以进行运算了。但是这里还有一个问题,我们要计算,于是也得计算,同理也还得继续求出,依次类推。因此我们可以优先预处理出来1到1e5的模数的欧拉函数,之后如果遇到字符‘2’则直接调用即可。
代码:
#include
#define maxn 100010
using namespace std;
typedef long long ll;
ll powmod(ll a,ll n,ll mo){//快速幂
ll res=1;
while(n){
if(n&1){
res=res*a%mo;
}
n>>=1;
a=a*a%mo;
}
return res;
}
ll eular(ll n){//欧拉函数
ll res=n;
for(ll i=2;i*i<=n;i++){
if(n%i==0){
res-=res/i;
while(n%i==0){
n/=i;
}
}
}
if(n>1) res-=res/n;
return res;
}
ll mod[maxn];//模数
void init(){//预处理模数
mod[0]=1e9+7;
for(int i=1;i<=1e5+5;i++){
mod[i]=eular(mod[i-1]);
}
}
char str[maxn];
int main()
{
int t;
scanf("%d",&t);
init();
while(t--){
scanf("%s",str);
int cnt=0;
ll ans=0;
for(int i=0;str[i]!=0;i++){
if(str[i]=='2') cnt++;//有几个2就有多少个模数调用
}
for(int i=0;str[i]!=0;i++){
if(str[i]=='0'){
ans++;
ans%=mod[cnt];
}
else if(str[i]=='1'){
ans=(ans+ans+2)%mod[cnt];
}
else{
cnt--;
ans=3ll*(powmod(2ll,(ans+1)%mod[cnt],mod[cnt])-1+mod[cnt])%mod[cnt];
}
}
cout<