Codeforces Round #739 (Div. 3)

D - Make a Power of Two

题目关键信息: 随便删除,只能在右边加,前导零不自动删除

  • 因为要看看到底操作几次就可以变得和 2 k    ( 0 ≤ k ≤ 63 ) 2^k\ \ (0\le k\le 63) 2k  (0k63)相等,拿到题我们就先想暴力一点的做法,判断时间复杂度,再考虑优化.所以最暴力的就是直接枚举 2 k 2^k 2k,再与 n n n来作比较,看看需要操作几次
  • 计算时间复杂度: 1 0 4 ∗ 63 ∗ 9 10^4*63*9 104639< 1 0 8 − 9 10^{8-9} 1089,所以行.
  • 然后再来考虑怎么把 n n n 2 k 2^k 2k进行比较,来计算需要几个操作,这个其实就是字符串匹配,将 2 k 2^k 2k来匹配原来的 n n n,因为只能从左边添加字符,假设 2 k = 1024 2^k=1024 2k=1024所以 n n n中必须是有从 1 1 1 4 4 4连续且有顺序排列的才行,举个例子: n = 1052 n=1052 n=1052,匹配成功了 3 3 3个, n = 2052 n=2052 n=2052,匹配成功 0 0 0个,因为必须要删除完所有的才能加入 1 1 1.
  • 再考虑一点小小的优化和怎么写才好写
    • 2 k 2^k 2k预处理出来,放在一个数组里面方便每次用,减少时间复杂度.
    • n n n 2 k 2^k 2k都转换为字符串,方便处理.
  • n n n的字符串下长度为 L e n Len Len,匹配成功了 n o w now now个(注意, n o w now now指向的是下一个位置,所以要 n o w − − now-- now),当前 2 k 2^k 2k的长度是 l e n [ i ] len[i] len[i].最终的答案就是 l e n [ i ] − n o w + L e n − n o w len[i]-now+Len-now len[i]now+Lennow,其中 l e n [ i ] − n o w len[i]-now len[i]now n n n需要添加的, L e n − n o w Len-now Lennow是需要删除的.
注意:需要枚举到 2 63 2^{63} 263才行
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define maxn
#define int long long
using namespace std;
int n,T,a[100],len[100];
char b[100][100],s[100];
signed main(){
	int base=63;
	scanf("%lld",&T); a[1]=1;
	for(int i=2;i<=base;i++) a[i]=a[i-1]*2;
	for(int i=1;i<=base;i++){
		while(a[i]){
			b[i][++len[i]]=a[i]%10+'0';
			a[i]/=10;
		}
		reverse(b[i]+1,b[i]+1+len[i]);
	}
	while(T--){
		scanf("%s",s+1); int Len=strlen(s+1);
		int ans=10000;
		for(int i=1;i<=base;i++){
			int now=1;
			for(int j=1;j<=Len;j++){
				if(s[j]==b[i][now]) now++;
			}
			now--;
			ans=min(ans,len[i]-now+Len-now);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

E - Polycarp and String Transformation

首先思考一下给出的字符串那么长,到底该怎么去分割开,要是分割开,那就爽歪歪.

  • e v e r y w h e r e v r y w h r v r y h r v r h r v h v everywherevrywhrvryhrvrhrvhv everywherevrywhrvryhrvrhrvhv为例子
  • 正常分割 e v e r y w h e r e    v r y w h r    v r y h r    v r h r    v h    v everywhere \ \ vrywhr \ \ vryhr \ \ vrhr \ \ vh \ \ v everywhere  vrywhr  vryhr  vrhr  vh  v

所以不难看到,要想把他分出来还是有点难度的,但是每次会删除一个字符,我们再倒过来看看,先是只有一种字母,然后再是只有两种字符……有全部的字母,所以我们倒序枚举,从字符串的末尾开始向头开始枚举的话,就可以找到删除的顺序,因为后删除的字符肯定在后面还会出现的,而先删除的字符就只会在前面,会后被枚举到,因此,我们找到了删除的顺序

再来考虑原字符串是什么,先这样,我们统计一下每个字母在大字符串出现了多少次,结果是这样的:

e = 4 e=4 e=4, w = 2 w=2 w=2, y = 3 y=3 y=3, r = 8 r=8 r=8, h = 5 h=5 h=5, v = 6 v=6 v=6.

每个字符在每次轮回的时候出现次数都是一样的,在删除了它之前是一个特定值 x x x,删除后就是 0 0 0,所以我们将所有的字符出现次数除上它是第几个被删除掉的,结果就是这样了

e = 4 e=4 e=4, w = 1 w=1 w=1, y = 1 y=1 y=1, r = 2 r=2 r=2, h = 1 h=1 h=1, v = 1 v=1 v=1

然而母串,也就是原字符串中,每个字母出现次数也是也么多次.

至此,我们已经求出了字符串和顺序,要考虑 − 1 -1 1就简单了,就是模拟题目所说过程,用我们得到的字符串去看,行不行就好了

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define maxn 600000
#define int long long
using namespace std;
int n,T,shunxu[maxn],book[maxn],cnt[maxn],k,flag,anslen;
char s[maxn],Ans[maxn],ans[maxn],Anss[maxn];
signed main(){
	scanf("%lld",&T);
	while(T--){
		scanf("%s",s+1); int len=strlen(s+1);
		for(int i=len;i;i--){
			cnt[s[i]]++;
			if(book[s[i]]) continue;
			book[s[i]]=true;
			shunxu[++k]=s[i];
		}
		reverse(shunxu+1,shunxu+1+k);
		for(int i=1;i<=k;i++) cnt[shunxu[i]]/=i;
		for(int i=1;i<=len;i++){
			cnt[s[i]]--;
			if(cnt[s[i]]==-1){
				for(int j=1;j<i;j++) ans[j]=Ans[j]=Anss[j]=s[j];
				anslen=i-1;
				break;
			}
		}
		for(int i=1,now=1,nowk=1,nowlen=anslen;i<=len;i++){
			if(i==len && now) now;
			if(now>nowlen){
				nowlen=0;
				memset(Anss,0,sizeof Anss);
				for(int j=1;j<=anslen;j++){
					if(Ans[j]==shunxu[nowk]) Ans[j]='*';
					if(Ans[j]=='*') continue;
					Anss[++nowlen]=Ans[j];
				}
				nowk++; now=1;
			}
			if(s[i]!=Anss[now]) {flag=true; break;}
			now++;
		}
		if(k==1) printf("%s %c\n",s+1,s[1]);
		else if(flag) printf("-1\n");
		else{
			for(int j=1;j<=anslen;j++) printf("%c",ans[j]);
			putchar(' ');
			for(int j=1;j<=k;j++) printf("%c",shunxu[j]);
			putchar('\n');
		}
		for(int i=1;i<=k;i++) book[shunxu[i]]=0,cnt[shunxu[i]]=0;  k=0; flag=false;
	}
	return 0;
}
注意:要memset 反例:aaabbb

F - Nearest Beautiful Number

首先因为要最后的数字越小越好,所以大的位越小越好,所以前面的数字尽可能的不变,让后面的数字去变,所以从左到右先找到第一个位置,这个位置之前的数字种类是 k k k种,加上这一位就 k + 1 k+1 k+1种了,所以的话,这一位就是不能要了,这一位就最好变成比原来那个数字大,但是尽可能小的数字,然后后面补 0 0 0就行了,但是还是有问题, 0 0 0也是一种数字,所以可能不能补 0 0 0,要补一种比 0 0 0大,但是尽可能小的数字,然后我们发现,补数字这个操作就相当于我们前面的那个操作,所以这两个操作合到一起就行了.

#include<cstdio>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
int T,n,k;
int cnt_digit(int x){
	int book[11]; memset(book,0,sizeof book);
	while(x){book[x%10]=1;x/=10;}
	for(int i=0;i<=9;i++) book[10]+=book[i];
	return book[10];
}
signed main(){
	scanf("%lld",&T);
	while(T--){
		scanf("%lld %lld",&n,&k);
		while(cnt_digit(n)>k){
			int l=1,r=n;
			while(cnt_digit(r)>k) l*=10,r/=10;
			l/=10; 
			n=((n/l)+1)*l;
			printf("%lld\n",n);
		}
	}
	return 0;
}

总结 :这种题目也不用想时间复杂度,除了一个一个的枚举,就算是暴力也把它写出来也能当对拍,所以要大胆写


关注+点赞 😍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

d3ac

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值