国庆 day 6 上午
1. 角谷猜想
(kakutani.pas/c/cpp)
(kakutani.in/out)
时间限制:1s/空间限制:256M
【题目描述】
某个名字末尾是 654321 的小 A 同学是个大家眼中公认的学霸(虽然他永远
不承认),他对题目的直觉到了一种可怕的地步,一眼看出题目的算法对他而言
只是小 Case,他甚至能在看到一个证明的瞬间敏锐地判断出这个证明的真伪。
现在小 A 同学机缘巧合地看到了角古猜想 (即对于 x 当它为奇数则 1 3 x x ,
为偶数,则
2
x
x ,一直重复这个步骤,则 x 最终会变为 1),在看完这个猜想的
一瞬间,他的直觉就来了——他认为角古猜想一定是错的!然后——他立刻就能
找出反例!
他立刻在纸上写满了 ) 00 10 1 ( n n 个小于 ) 10 0 ( 10
4
L
L
的正整数, 打算放
到他的 grand super computer 上去跑,可是他突然觉得有些正整数不是很吉利,
可能会干扰到他的最终结果,所以他打算把一些正整数加工一下。
小 A 觉得 4、7、13 都是不吉利的数字,所以要把所有正整数里的 4、7、13
都去掉,如果去掉后得到的新数字里依旧有 4、7、13,那么就要继续删掉,直
到最后的数组不存在 4、7、13,它才是一个吉利的数字。
例如 1 => 113 => 11133 => 111733 => 1411733
特别规定,如果最后所有数字都被删掉了,就输出 0
小A觉得这个枯燥的工作不适合他这样的天才, 于是就把这个工作交给了你。
当然, 只要你能顺利解决,小 A 承诺会在那篇将会震惊世界的论文的特别感谢栏
上署上你的大名。
【输入格式】 (kakutani.in)
一共 1 n 行。
第一行一个正整数 ) 0 10 1 ( n n ,表示数字个数。
接下来每行一个正整数 x 。
【输出格式】 (kakutani.out)
一共 n 行。
每行一个正整数,表示输入每个 x 对应的答案。
【输入输出样例】
kakutani.in kakutani.out
5
13713
141713
1333333372589
1411733
2147483647
0
11
3333332589
1
21836
【数据规模约定】
对于 10% 的数据, 2147483647 0 x
对于另外 10% 的数据,给定的数字 x 没有数码 3
对于另外 10% 的数据, 1 n
对于全部的数据, ) 00 10 1 ( n n ,
Lx 10 1 )
思路:栈维护一下。
吐槽:学校的机子跑的太慢了,竟然卡我输出。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int n,bns[101000],vis[101000]; char num[101000]; int main(){ freopen("kakutani.in","r",stdin); freopen("kakutani.out","w",stdout); scanf("%d",&n); while(n--){ scanf("%s",num); int len=strlen(num),pos=0,ff=0; memset(vis,0,sizeof(vis)); for(int i=0;i<len;i++){ if(num[i]=='4') vis[i]=1; else if(num[i]=='7') vis[i]=1; else if(num[i]=='1') bns[++pos]=i; else if(num[i]=='3'&&pos){ vis[i]=1; vis[bns[pos--]]=1; } else pos=0; } for(int i=0;i<len;i++) if(!vis[i]){ ff=1; putchar(num[i]); } if(!ff) putchar('0'); printf("\n"); } } /* 4 123131313473 */
2. 刀塔
(dota.pas/c/cpp)
(dota.in/out)
时间限制:2s/空间限制:256M
【题目描述】
事情要从小 A 的朋友小 S 说起,小 S 是个刀塔狂热粉,他每天除了学习就是
在打刀塔。然而让小 S 很苦恼的事情是,他发现最近他似乎遇见瓶颈了,他发现
他每次输的时候都是雪崩, 赢的时候都是躺赢, 完全发挥不出自己应该有的实力。
他上贴吧请教了三分钟辉耀羊刀的万分大神,接到了万分大神的圣旨:去看自己
的录像反省反省。于是小 S 决定利用十一假期的时间好好反省反省。
小 S 调出了自己前段时间的游戏录像,一共有 N 个录像,他给每个录像一个
正整数表示这个录像的观看价值。 现在他决定从里面找到二组连续的录像来观看,
这两组录像的要求如下:
1.两组录像里每一组的录像数量都不能小于 A,不然没有意义
2.他看的总录象的数量一定要超过 B,他相信看的越多就越好
3.小 S 觉得两组录像时间隔的太近没有意义,因为很有可能前后两段暴露的
问题基本一致,但是他又觉得隔的太远也干扰他去思考自己当时的状态。所以他
要求这两段录像中间应该刚好隔了 K 个录像
好吧,这已经够让人烦躁的了,但是小 S 还不满足,他觉得这样的挑选方案
依旧很多,所以他想挑选足够好的方案,一个质量足够高的方案——也就是观看
价值最低的录像的观看价值要尽可能的高。
【输入格式】 (dota.in)
一共 2 行。
第一行四个正整数 K B A N 、 、 、 。
接下来一行 N 个正整数,表示每个录像的观看价值 ) 10 1 (
9
x x 。
【输出格式】 (dota.out)
一共 1 行,表示最佳方案中观看价值最低的录像的观看价值。
【输入输出样例 1】
dota.in dota.out
10 2 5 3
7 8 2 3 1 6 4 10 5 9
4
【输入输出样例 2】
dota.in dota.out
20 3 9 3
54867025 259306632 473619223 170507035
347936959 421059860 246006182 948910354
630205869 541359081 574152766 665959900
843439075 445125437 774018043 719562887
705993886 133173428 256457367 708196876
246006182
【数据规模约定】
保证 B A A , N K B
对于 10% 的数据, 1000 100 N
对于另外 10% 的数据, B A A
对于另外 30% 的数据, 100 B
对于全部的数据, ) 10 100 (
6
N N , 0 000 10 10 K B,
【样例解释】
对于样例 1,最优方案为选择 7 8 2 3 1 6 4 10
40分暴力
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 1000010 using namespace std; int x,y; int asd,qwq; int A,B,k,ans; int t,q,n,mx,nm; int a[N]; int stmin[N][20],mn[N]; void init(){ mn[0]=-1; for(int i=1;i<=n;i++){ mn[i]=((i&(i-1))==0)?mn[i-1]+1:mn[i-1]; stmin[i][0]=a[i]; } for(int j=1;j<=mn[n];j++) for(int i=1;i+(1<<j)-1<=n;i++) stmin[i][j]=min(stmin[i][j-1],stmin[i+(1<<(j-1))][j-1]); } int rmq_min(int L,int R){ int k=mn[R-L+1]; return min(stmin[L][k],stmin[R-(1<<k)+1][k]); } int main(){ freopen("dota.in","r",stdin); freopen("dota.out","w",stdout); scanf("%d%d%d%d",&n,&A,&B,&k); for(int i=1;i<=n;i++) scanf("%d",&a[i]); init(); for(int i=1;i<=n-B-k+1;i++){ mx=B-A; for(int j=A;j<=mx;j++){ nm=B-j; asd=rmq_min(i,i+j-1); qwq=rmq_min(i+j+k,i+j+k+nm-1); ans=max(ans,min(asd,qwq)); } } cout<<ans; }
正解思路:
以下解题思路来自xxy大佬的博客。
枚举i为间隔K个录像的左端点,那么间隔录像为[i,i+k-1]
设第一段为[Sa,Ta],第二段为[Sb,Tb],Ma为min[Sa,Ta],Mb为min[Sb,Tb]
随着Sa的左移,Ma单调不增
随着Tb的右移,Mb单调不增
如果枚举Sa,则有以下式子: Ta-Sa+1+Tb-Sb+1>=B
即Tb>=B+Sa+Sb-Ta-2
因为Tb右移,Mb单调不增,所以Tb取等号最优
所以二分Sa的位置,用st表查询Ma,Mb
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define MAXN 1000001 using namespace std; int n,p,k,A,B; int logg2[MAXN]; int st[MAXN][21]; void stpre(){ p=log2(n); for(int j=1,k=2;j<=p;j++,k<<=1) for(int i=1;i+k-1<=n;i++) st[i][j]=min(st[i][j-1],st[i+(k>>1)][j-1]); for(int i=1;i<=n;i++) logg2[i]=log2(i); } int getmin(int s,int t){ p=logg2[t-s+1]; int len=1<<p; return min(st[s][p],st[t-len+1][p]); } int main(){ freopen("dota.in","r",stdin); freopen("dota.out","w",stdout); scanf("%d%d%d%d",&n,&A,&B,&k); for(int i=1;i<=n;i++) scanf("%d",&st[i][0]); stpre(); int Sa,Sb,Ta,Tb,Ma,Mb,Mi; int ans=0; int l,r,mid,tmp; for(int i=A+1;i<=n-A-k+1;i++){ Ta=i-1; Sb=i+k; Tb=max(Sb+A-1,B+i-A+Sb-Ta-2); Ma=getmin(i-A,Ta); Mb=getmin(Sb,Tb); l=1;r=i-A;tmp=min(Ma,Mb); while(l<=r){ mid=(l+r)/2; Tb=max(Sb+A-1,B+mid+Sb-Ta-2); Ma=getmin(mid,Ta); Mb=getmin(Sb,Tb); tmp=max(tmp,min(Ma,Mb)); if(Ma<Mb) l=mid+1; else if(Ma==Mb) break; else r=mid-1; } ans=max(ans,tmp); } printf("%d",ans); }
3. 反击数
(spenum.pas/c/cpp)
(spenum.in/out)
时间限制:2s/空间限制:256MB
【题目描述】
上次说到小 A 在你的帮助下,在反证角谷猜想的路上已经看见了曙光,他相
信自己即将为这个著名难题画上最后的句点,小 A 十分地兴奋,结果他那微不足
道的老毛病又犯了——他忍不住想炫耀一番,结果搞得朋友圈内人尽皆知,当然
也就传到了另一位学霸小 H 的耳中。
虽然表面上小 H 和小 A 是挚友,但其实不难想象,这两个天才在私下里早已
将对方视为此生必须要战胜的对手。和天赋异禀的小 A 不同,小 H 拥有的是逆天
的气运——经常他选择题都不需要看选项就能全对 (据说这也是曾经笃信唯物主
义的小 A 为什么变得神经质般迷信的根本原因) 现在小 H 要正式向小 A 在角谷猜
想上发起反击, 不过两个学霸在某一点上倒是达成了一致——这个猜想一定是错
的,所以它必然有反例!
对自己的气运抱有足够自信的小 H 打算用这样的方式来找到反例:他先选定
一个自己的幸运数 X,他认为所有中间出现了 X 的数都是“扩展幸运数” (包括
X 自身) (例:若 X=69 那么 84576901 就是一个“扩展幸运数” ,在 84576 后面
出现了数码 69,而 679 则不是一个“扩展幸运数” ),然后小 H 会再精心挑选一
个“命运数”K,最后小 H 将在随机生成的正整数区间[L, R]中选择第 K 大的“扩
展幸运数” ,用它去验证角谷猜想。小 H 认为这样的速度一定快过小 A 那个落后
的办法。
好了,现在同样的工作摆在了你的面前,你需要帮助小 H 得到那个数字。
PS:小 H 有时候会突然无征兆地打瞌睡(为了保养他的运气) ,所以可能正
整数区间[L, R]中并没有 K 个“扩展幸运数” ,这时候输出“Hey,wake up!” (不
含引号)
【输入格式】 (spenum.in)
输入仅有一行,共 4 个数字,按次序分别为 L,R,X,K
【输出格式】 (spenum.out)
输出为一行,即正整数区间[L, R]中第 K 大的“扩展幸运数”
【输入输出样例 1】
spenum.in spenum.out
1 1000 6 14 67
【输入输出样例 2】
spenum.in spenum.out
1 1 2 1 Hey,wake up!
【样例解释】
前 14 个数字分别是 6、16、26、36、46、56、60、61、62、63、64、65、66、67
【数据规模约定】
对于 10% 的数据 : 1000000 1 L R
对于另外 10% 的数据 : 1 K
对于另外 20% 的数据 : 9 1 X
对于 100% 的数据 :
18
10 , , , 1 K X R L
思路:
数位DP
dp[i][j][0/1] 前i位匹配到X的第j位,是否已经包含1个X的数的个数
二分,计算<=mid的数里的答案
其中的匹配用kmp
这个dp的清空可以只在最开始清空一次。
原因在这里:
第一:memset(dp,-1,sizeof dp);放在多组数据外面。 这一点是一个数位特点,使用的条件是:约束条件是每个数自身的属性,而与输入无关。 具体的:上一题不要62和4,这个约束对每一个数都是确定的,就是说任意一个数满不满足这个约束都是确定,比如444这个数,它不满足约束条件,不管你输入的区间是多少你都无法改变这个数不满足约束这个事实,这就是数自身的属性(我们每组数据只是在区间计数而已,只能说你输入的区间不包含444的话,我们就不把它统计在内,而无法改变任何事实)。 由此,我们保存的状态就可以一直用(注意还有要limit,不同区间是会影响数位在有限制条件下的上限的) 这点优化就不给具体题目了,这个还有进一步的扩展。不过说几个我遇到的简单的约束: 1.求数位和是10的倍数的个数,这里简化为数位sum%10这个状态,即dp[pos][sum]这里10 是与多组无关的,所以可以memset优化,不过注意如果题目的模是输入的话那就不能这样了。 2.求二进制1的数量与0的数量相等的个数,这个也是数自身的属性。 3.。。。。。 还是做题积累吧。搞懂思想! 下面介绍的方法就是要行memset优化,把不满足前提的通过修改,然后优化。 介绍之前,先说一种较为笨拙的修改,那就是增加状态,前面讲limit的地方说增加一维dp[pos][state][limit],能把不同情况下状态分别记录(不过这个不能memset放外面)。基于这个思想,我们考虑:约束为数位是p的倍数的个数,其中p数输入的,这和上面sum%10类似,但是dp[pos][sum]显然已经不行了,每次p可能都不一样,为了强行把memset提到外面加状态dp[pos][sum][p],对于每个不同p分别保存对应的状态。这里前提就比较简单了,你dp数组必须合法,p太大就G_G了。所以对于与输入有关的约束都可以强行增加状态(这并不代表能ac,如果题目数据少的话就随便你乱搞了)
或者戳这里
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int len; char X[20]; long long L,R,K; int net[20],num[20]; long long l,r,mid,ans; long long dp[20][20][2]; void KMP(){ len=strlen(X); int j; for(int i=1;i<len;i++){ j=net[i]; while(j&&X[i]!=X[j]) j=net[j]; net[i+1]=X[i]==X[j]?j+1:0; } } long long dfs(int pos,int stact,bool limite,bool get){ if(pos==-1) return get; if(!limite&&dp[pos][stact][get]!=-1) return dp[pos][stact][get]; int up=limite?num[pos]:9; long long tmp=0; int j; for(int i=0;i<=up;i++){ j=stact; while(j&&X[j]-'0'!=i) j=net[j]; if(X[j]-'0'==i) j++; tmp+=dfs(pos-1,j,limite&&(i==up),get||(j==len)); } if(!limite) dp[pos][stact][get]=tmp; return tmp; } long long slove(long long now){ int pos=0; while(now){ num[pos++]=now%10; now/=10; } return dfs(pos-1,0,1,0); } int main(){ freopen("spenum.in","r",stdin); freopen("spenum.out","w",stdout); scanf("%I64d%I64d%s%I64d",&L,&R,X,&K); KMP(); memset(dp,-1,sizeof(dp)); long long tmp=slove(L-1); if(slove(R)-slove(L-1)<K){ puts("Hey,wake up!"); return 0; } l=L;r=R; while(l<=r){ mid=(l+r)/2; if(slove(mid)-tmp<K) l=mid+1; else{ ans=mid; r=mid-1; } } printf("%I64d",ans); }