1560D. Make a Power of Two(思维,双指针)
题意:
给定一个数n,问最少需要多少次操作,能够将其变为2的幂次数。(n ≤1e9)
1.选择一个数位,删掉。
2.在最后面增加一个数位,为任意值。
思路:
仅仅由所给数n来判断最终能变成2的那么次幂数是很难的。
但是2的次幂数一共就60个,所以可以遍历所有的幂次数x,判断当前值变为x所需要的最小操作数。取所有数答案的最小值。
那么数n变为幂次数x最小需要操作多少次呢?
由于增加一个数位操作只能增加到最后一位,所以数n只能对幂次数前面的数位有贡献,然后在后面补充剩下的,构成幂次数x。(贡献的前面数位必须是连续的。如果中间有间隔,只能在后面添加,那么无论如何都是不能加上去的)
所以就要看数n最多能对幂次数前面多少数位有贡献了。
遍历幂次数的每一位,看这一位能不能贡献到,如果能,继续往后走;如果不能,就说明这一位无法贡献,停止。
双指针。
找到最大贡献值cnt。那么数n的其他数位要删掉,幂次数x后面的数位要补上。
所以需要的操作数为 len1+len2-2*cnt
。
Code:
const int N=100010;
int n,m,T;
string a,b,s[N];
int main(){
Ios;
ll x=1;
for(int i=0;i<62;i++)
{
if(i) x*=2;
s[i+1]=to_string(x);
}
cin>>T;
while(T--)
{
cin>>a;
int ans=1e9;
for(int k=1;k<=62;k++)
{
string x=s[k];
int j=0,cnt=0;
for(int i=0;i<x.size();i++)
{
while(j<a.size()-1&&x[i]!=a[j]) j++;
if(x[i]==a[j]) j++,cnt++;
else break;
}
int t=x.size()+a.size()-2*cnt;
ans=min(ans,t);
}
cout<<ans<<endl;
}
return 0;
}
扩展:
如果可以将任意位置删除,可以从任意位置添加,问将一个数变为2的幂次数需要的最小操作数?
这就可以将原串和所有2的幂次数分别求最长公共子序列,取操作数最小的。
const int N = 200010, mod=1e9 + 7;
int T, n, m;
string s[N];
int f[50][50];
int main(){
ll x=1;
for(int i=0;i<31;i++){
if(i) x*=2;
s[i+1]=to_string(x);
}
cin>>T;
while(T--)
{
string a;cin>>a;
n=a.size();
a=" "+a;
int ans=1e9;
for(int k=1;k<=31;k++)
{
string x=" "+s[k];
int m=x.size()-1;
mem(f,0);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
f[i][j]=max(f[i][j],f[i-1][j]);
f[i][j]=max(f[i][j],f[i][j-1]);
if(a[i]==x[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
}
}
ans=min(ans,n+m-2*f[n][m]);
}
cout<<ans<<endl;
}
return 0;
}
1245C. Constanze’s Machine(组合,斐波拉契数)
题意:
给定一个修改过的字符串,问原串有多少种?
原串中的字符m可能被修改为nn,w可能被修改为uu。
思路:
可以由若干个 nnn…能够还原为m的方式数和uuu…能够还原为w的方式数 相乘。组合原理。
那么nnn…能够还原为m的方式数为多少呢?
有一对n变成m的情况,两对n变成m的情况,三对…
如果有两对n变为m,那么总个数为n-2,还原出的串个数为:
A
n
−
2
n
−
2
/
(
A
2
2
∗
A
n
−
4
n
−
4
)
A_{n-2}^{n-2}/(A_2^2*A_{n-4}^{n-4})
An−2n−2/(A22∗An−4n−4)。
其规律是这样的:1,2,3,5,8,13,21…斐波拉契数列。
Code:
const int N = 200010, mod=1e9 + 7;
int T, n, m, a[N];
int f[N];
void pre(){
f[1]=1,f[2]=2,f[3]=3;
for(int i=4;i<=1e5;i++) f[i]=(f[i-1]+f[i-2])%mod;
}
int main(){
Ios;
string s;
cin>>s;
n=s.size();
s=" "+s;
int flag=0;
for(int i=1;i<=n;i++) if(s[i]=='m'||s[i]=='w') flag=1;
if(flag){
cout<<0;return 0;
}
pre();
ll ans=1,cnt=0;
for(int i=0;i<s.size();i++)
{
if(s[i]=='u')
{
cnt=0;
int j;
for(j=i;j<s.size();j++)
{
if(s[j]!='u') break;
cnt++;
}
i=j-1;
ans=ans*f[cnt]%mod;
}
else if(s[i]=='n')
{
cnt=0;
int j;
for(j=i;j<s.size();j++)
{
if(s[j]!='n') break;
cnt++;
}
i=j-1;
ans=ans*f[cnt]%mod;
}
}
cout<<ans;
return 0;
}
也可以用dp实现:
f[i]表示,前i个位置可以转化为原串的方案数。
初始值f[0]设为1.
如果当前位置可以和前一位置合并的话,f[i] = f[i-2]+f[i-1]
,可以和前一位置合并,那么方案数为f[i-2],也可不合并,方案数为f[i-1]。
const int N = 200010, mod=1e9 + 7;
int T, n, m, a[N];
int f[N];
int main(){
Ios;
string s;
cin>>s;
n=s.size();
s=" "+s;
int flag=0;
for(int i=1;i<=n;i++) if(s[i]=='m'||s[i]=='w') flag=1;
if(flag){
cout<<0;return 0;
}
f[0]=1;
for(int i=1;i<=n;i++)
{
f[i]=f[i-1];
if(s[i]=='u'&&s[i-1]=='u'||s[i]=='n'&&s[i-1]=='n')
f[i]=(f[i-1]+f[i-2])%mod;
}
cout<<f[n];
return 0;
}