国庆 day 6 上午

Posted 一蓑烟雨任生平

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了国庆 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
*/
View Code

 

 

 

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;
}
View Code

正解思路:

以下解题思路来自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);
}
View Code

 

 

 

 

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,如果题目数据少的话就随便你乱搞了)
View Code

或者戳这里

#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);
}
View Code

 

以上是关于国庆 day 6 上午的主要内容,如果未能解决你的问题,请参考以下文章

qbxt济南国庆刷题班游记

国庆day2

2017北京国庆刷题Day1 morning

清北学堂国庆day2解题报告

day 23,键盘膜来了~~~~~~~~

2019牛客国庆集训派对day1 D.Modulo Nine(巧妙的dp)