• 7.10校内测


    7.9刚考了,今天又考ρωρ

    T1 高级打字机

    这里undo可以撤销undo操作emm可真是神奇

    先说一下50pts:直接开数组模拟

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    #include<stack>
    using namespace std;
    int read()
    {
        char ch=getchar();
        int x=0;bool f=0;
        while(ch<'0'||ch>'9')
        {
            if(ch=='-')f=1;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9')
        {
            x=(x<<3)+(x<<1)+(ch^48);
            ch=getchar();
        }
        return f?-x:x;
    }
    int n,u;
    char zs[100009];
    int main()
    {
        freopen("type.in","r",stdin);//考试不要忘了
        freopen("type.out","w",stdout);
        n=read();
        for(int i=1;i<=n;i++)
        {
            char cz=getchar();
            while(cz!='T'&&cz!='U'&&cz!='Q')cz=getchar();
            if(cz=='T')
            {
                char ty=getchar();
                while(ty==' '||ty=='
    '||ty=='
    ')ty=getchar();
                zs[++u]=ty;
            }
            if(cz=='Q')
            {
                int num=read();
                if(num>u)printf(" 
    ");
                else printf("%c
    ",zs[num]);
            }
            if(cz=='U')
            {
                int num=read();
                u-=num;
            }
        }
    }
    50pts

    来我们考虑剩下那50怎么拿

    当然是用可持久化线段树或者主席树做了

    既然可以撤销撤销操作,那么我们可以把每一步操作后的状态都记录下来。在这里用栈模拟还是很方便的,所以我们就可以开一堆栈,第i个栈表示进行完第i次修改操作(不包含查询操作)后的状态记录下来,就像下面这样。

    注意这是4个不同的栈

    这样我们就可以知道进行完当前操作,栈长什么样子了

    不过.....空间爆炸警告

    操作几次就有几个栈,操作1e6次不是RE就是MLE(还可能TLE,或者交出个)。

    那我们究竟把空间浪费在哪了呢?

    数据很有可能一直在插入字符,当插入了几百个之后开始不断的撤销撤销撤销。这时候我们按照上面的方法,每次都要拷贝几百个不会被影响到字符。这在空间和时间上都造成很很很大的浪费。

    那我们就偷个懒,不拷贝了。我们维护一个链表当前的顶部,如果有撤销操作,就让当前这个顶部和x步(就是撤销的步数)前的那个链表顶部相同。

    画个图吧

    数字是第几步,数字下面是这一步的操作。

    这样插入和删除都没有问题了。接下来就是查询了。emmm好像复杂度炸了?!

    上面的查询复杂度是O(top-x)的(x就是第几个数),显然如果数据中查询过多会TLE对吧。

    so我们下一步是优化查询。

    数组查询很快,但是插入,撤销很慢,链表插入,撤销很快,但查询很慢,可不可以让它们中和一下?

    这里有个神奇的东西叫块状数组,其实就是个链表。之前的链表每个节点只能存一个数,现在我们让每个节点存B个数(这里的B是自己确定的,wz用提交答案进行二分最后认为在这道题里面B=2000左右比较好)(wz神仙还说这题B=√¯n

    总之我们这里让B=√n好了。

    插入和撤销还是上面的思路,只是当插入的时候,我们判断当前这个块有没有满,没满:直接在后面插入,满了:新建一个块,再插入。

    查询:我们知道了x和一个块的大小B,则我们可以先跳过x/B个块,然后再从x所在的块里面寻找x,这样比一个一个往后找快多了。不过这里我们还是倒着找的,所以x=size-x(size是当前字符的总个数)

    那这个题除了不会写代码是不是就没有任何的问题了?

    好的我们直接看wz神仙的代码

    #include <cmath>//接下来是同zay一样令人窒息的指针
    #include <cstdio>
    #include <cstring>
    template <class T>
    inline void read(T &num)
    {
        bool flag = 0;
        num = 0;
        char c = getchar();
        while ((c < '0' || c > '9') && c != '-')
            c = getchar();
        if (c == '-')
        {
            flag = 1;
            c = getchar();
        }
        num = c - '0';
        c = getchar();
        while (c >= '0' && c <= '9')
            num = (num << 3) + (num << 1) + c - '0', c = getchar();
        if (flag)
            num *= -1;
    }
    inline void read(char &c)
    {
        do
        {
            c = getchar();
        } while (c == ' ' || c == '
    ' || c == '
    ');
    }
    template <class T>
    inline void output(T num)//神仙的快输
    {
        if (num < 0)
        {
            putchar('-');
            num = -num;
        }
        if (num >= 10)
            output(num / 10);
        putchar(num % 10 + '0');
    }
    template <class T>
    inline void outln(T num)
    {
        output(num);
        putchar('
    ');
    }
    template <class T>
    inline void outps(T num)
    {
        output(num);
        putchar(' ');
    }
    template <class T>
    inline T max(T a, T b) { return a < b ? b : a; }
    const int N = 100000;
    int n, B;
    struct node
    {
        char *s;//s是一个字符串
        int now, pc;//now是存到了当前块的第几个位置,pc是统计当前有几个块
        node *pre;//pre是前驱
        node()
        {
            now = pc = 0;
            s = new char[B];
            pre = NULL;
        }
        void copy(node *src)//拷贝一个一毛一样的节点
        {
            now = src->now;
            pc = src->pc;
            pre = src->pre;
            for (int i = 0; i < now; i++)
            {
                s[i] = src->s[i];
            }
        }
    } * head[N];
    int lst;
    void append(char x)//插入
    {
        if (head[lst]->now == B)
        {
            head[++lst] = new node;
            head[lst]->pre = head[lst - 1];
            head[lst]->pc = head[lst - 1]->pc + 1;
            head[lst]->s[head[lst]->now++] = x;
        }
        else
        {
            head[++lst] = new node;
            head[lst]->copy(head[lst - 1]);
            head[lst]->s[head[lst]->now++] = x;
        }
    }
    char query(int x)
    {
        int siz = head[lst]->now + head[lst]->pc * B;//siz就是size,当前元素的个数
        x = siz - x + 1;
        if (x <= head[lst]->now)
        {
            return head[lst]->s[head[lst]->now - x];
        }
        x -= head[lst]->now;//先跳过最后一个块
        node *now = head[lst]->pre;
        while (x > B)//一直跳块
        {
            now = now->pre;
            x -= B;
        }
        return now->s[B - x];
    }
    int main()
    {
        read(n);
        B = sqrt(n);
        head[0] = new node;
        for (int i = 1; i <= n; i++)
        {
            char op;
            read(op);
            if (op == 'T')
            {
                char x;
                read(x);
                append(x);
            }
            if (op == 'U')
            {
                int x;
                read(x);
                head[lst + 1] = head[lst - x];//节点是一毛一样的
                lst += 1;
            }
            if (op == 'Q')
            {
                int x;
                read(x);
                putchar(query(x));
                putchar('
    ');
            }
        }
    }

    T2 不等数列

    看到这一个题,我们想起了昨天那到斐波那契数列。怎么做呢?打表啊。

    但是这个题就算打出来表也看不出来神马规律

    我们还是用正常的思维推理一下吧

    设f[i][j]为n=i,k=j时的答案

    我们假设我们已知f[n-1][k],这时候我们需要插入一个n。应该怎么插入呢?

    我们插入的地方有4中可能

    ①:在序列的首部

    ②:在序列的尾部

    ③:插入在n-1形成的序列的''>''处

    ④:在''<''处

    ①:原来的''>''数量+1,''<''数量不变(即k不变)

    ②:''<''数量+1(k+1)

    ③:由于插在原来的''>''处,且n>1到n-1中的任意一个数,所以''<''数量+1,''>''数量不变,即k+1

    ④:与③相反,''<''数量不变,''>''数量+1,k不变

    这样,f[n][k]就可以由f[n-1][k]和f[n-1][k-1]得到

    由f[n-1][k]转移时,要么是①,要么是④。f[n-1][k]有几个地方是小于号,就要乘几。f[n-1][k]当然有k个地方是小于号喽(第二维都是k了),再加上在首部插入,所以f[n-1][k]对答案的贡献是f[n-1][k]*(k+1)

    由f[n-1][k-1]转移时,情况有②和③。由于n-1个数有n-2个空,其中小于号有k-1个,所以大于号就是(n-2-(k-1))=n-k个

    综上,f[n][k]=f[n-1][k]*(k+1)+f[n-1][k-1]*(n-k)

    代码:

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    #include<stack>
    using namespace std;
    int read()
    {
        char ch=getchar();
        int x=0;bool f=0;
        while(ch<'0'||ch>'9')
        {
            if(ch=='-')f=1;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9')
        {
            x=(x<<3)+(x<<1)+(ch^48);
            ch=getchar();
        }
        return f?-x:x;
    }
    int n,k,dp[1009][1009];
    const int mod=2012;
    int main()
    {
        freopen("num.in","r",stdin);
        freopen("num.out","w",stdout);
        n=read();k=read();
        if(k==n-1)
        {
            printf("1");return 0;
        }
        for(int i=1;i<=n;i++)//由暴力打表可得dp[i][0]=1
         dp[i][0]=1;
        for(int i=1;i<=k;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(j<i)continue;
                dp[j][i]=dp[j-1][i]*(i+1)+dp[j-1][i-1]*(j-i);
                dp[j][i]=(dp[j][i]+mod)%mod;
            }
        }
        printf("%d",dp[n][k]);
    }

    T3:经营与开发

    看到这个题,依旧没有什么想法emmmm

    所以这就是我爆搜只有10分的理由

    我们直接来看正解吧

    这道题是真•dp

    因为是真•dp,所以我们要倒着dp

    我们发现这个在当前星球的决策会影响到下一个星球,据大佬说这叫有后效性,so我们可以倒着dp。

    看这几个式子

    好像不管我们干啥事都有p的事。你看这个p是不是很烦?那我们就把p都提出来,最后再乘进去。

    那究竟怎么dp呢?对于每一个星球,我们有两种选择。一:进行一番操作。二:什么也不做。用dp[i]表示在i星球得到的最大金钱,那dp[i]肯定是在上面两种选择中选择一个金钱最大的。

    什么都不做,那肯定就是dp[i+1](我们倒着dp)

    每个星球都有类型,我们考虑如果当前星球是类型1,则开采获得的金钱为a[i],由于钻头的损坏,dp[i+1]的贡献就变成了dp[i+1]*(1-0.01k)(注意这里已经将p提出来了,就不管p的事了)。那么开采就是a[i]+dp[i+1]*(1-0.01k),dp[i]=max(dp[i+1],a[i]+dp[i+1]*(1-0.01k))

    如果当前星球是类型2,则维修消耗的金钱是b[i],钻头经过维修,dp[i+1]的贡献就是dp[i+1]*(1+0.01c),所以dp[i]=max(dp[i+1],dp[i+1]*(1+0.01c)-b[i])

    最后dp[1]*w就是答案(我们之前提出了p,最后要乘回去)

    代码:

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    int read()
    {
        char ch=getchar();
        int x=0;bool f=0;
        while(ch<'0'||ch>'9')
        {
            if(ch=='-')f=1;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9')
        {
            x=(x<<3)+(x<<1)+(ch^48);
            ch=getchar();
        }
        return f?-x:x;
    }
    int n,w,c,k,a[100009];
    double dp[100009];
    int zy[100009];
    int main()
    {
            freopen("exploit.in","r",stdin);//别忘了加!!!
            freopen("exploit.out","w",stdout);
        n=read();k=read();c=read();w=read();
        for(int i=1;i<=n;i++)
        {
            zy[i]=read();
            a[i]=read();
        }
        double kk=1-0.01*(double)k;
        double cc=1+0.01*(double)c;
        for(int i=n;i>=1;i--)
        {
            if(zy[i]==1)
                dp[i]=max(dp[i+1],dp[i+1]*kk+a[i]);
            else dp[i]=max(dp[i+1],dp[i+1]*cc-a[i]);    
        }
        printf("%.2lf",1.0*dp[1]*w);
    }
  • 相关阅读:
    Python入门 日志打印
    Python入门 面向对象
    Python入门 更换pip源的方法
    Python入门 模块
    Python入门 函数式编程
    四月份该做的事情
    Docker入门 配置篇
    Python入门 序列章
    Python入门 值内存管理与所有的关键字
    论操作系统的IO
  • 原文地址:https://www.cnblogs.com/lcez56jsy/p/11166672.html
Copyright © 2020-2023  润新知