zoukankan      html  css  js  c++  java
  • CSUST--3.28排位周赛第六场 (全解)

     emmm,这次是DP场,不知道情况怎么样,蒟蒻的我在两个小时最多也就出个4题,压缩字符串那题对于蒟蒻的我来说确实有点难搞。。。

    然后看你们6分钟出了tomjobs的那题,21分钟出了期望DP的那题。。。我以为你们能AK的。。结果2个多小时后还是2题。。。。。不知道种花那题为啥没什么人写

    比赛链接:http://acm.csust.edu.cn/contest/83

    比赛过后无法提交,请到problem中提交

    题目说明:

    pph的篮球考试(期望DP)

    摸鱼的tomjobs(简单DP)

    万能代码(区间DP)

    序列变换(简单DP)

    种花(数学期望)

    pph的篮球考试

    题目大意:每个球的投中率为$frac{1}{a_i}$,问你分别投中$n,n-1,n-2,n-3,n-4,n-5$个球的概率(n<=1e6),输出答案在998244353下的逆元

    Sample Input 

    5
    2 3 4 5 6

    Sample Output

    641926577 644699478 658563983 686292993 196875970 166374059

    emmm,怎么说呢。。。一看10MB的限制内存就感觉是pph这个老阴比出的题目,又想来一手卡内存。大概看一下题目就知道是期望DP,那么根据上一次一步两步的经验我们应该知道这种10MB的东西的每一步的dp应该可以由上一步得出,也就是说我们的可以开dp[2][10]。。。。代表什么呢,我们先不忙着压缩内存,设有dp为dp[n][10],由于我们只需要最多5个球不中的概率,所以我们dp[i][j]可以代表第已经投了$i$次,有$j$次没有投中的概率。那么状态转移方程就很好写了:$dp[i][j]=dp[i-1][j]*frac{1}{a_i}+dp[i-1][j-1]*(1-frac{1}{a_i})$。。。其实这题和之前牛客寒训的期望DP有些类似,只不过那题更简单

    然后。。。我们又可以通过不断异或1来交错dp[0/1][10]的步骤了

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mod=998244353;
    typedef long long ll;
    const int mac=1e6+10;
    
    ll dp[2][10];
    
    ll qick(ll a,ll b)
    {
        ll ans=1;
        while (b){
            if (b&1) ans=ans*a%mod;
            a=a*a%mod;
            b>>=1;
        }
        return ans;
    }
    
    int main()
    {   
        int n;
        scanf ("%d",&n);
        int flag=1;
        dp[0][0]=1;
        for (int i=1; i<=n; i++){
            ll x;
            scanf ("%lld",&x);
            x=qick(x,mod-2);
            for (int j=0; j<=5; j++){//j个球没有投中的概率
                if (j>0)
                    dp[flag][j]=dp[flag^1][j]*x%mod+dp[flag^1][j-1]*(1-x+mod)%mod;
                else dp[flag][j]=dp[flag^1][j]*x%mod;
                dp[flag][j]%=mod;
            }
            flag^=1;
        }
        for (int i=0; i<6; i++){
            printf ("%lld%c",dp[flag^1][i],i==6?'
    ':' ');
        }
        return 0;
    }
    View Code

    摸鱼的tomjobs

    题目大意:给你n个数,你要选择其中某些数使得这些数的和最大,当你选择$a_i$时,值为$a_i-1,a_i+1$的数就不能选择了

    Sample Input 

    10
    1 1 2 2 3 3 4 4 5 5

    Sample Output 

    18

    这题我们可以用DP来写,这个DP的话非常简单,设该数取或者不取为$dp[1/0][i]$那么如果取了该数,则答案是由没有取$i-1$转移过来的。也就是说$dp[1][i]=dp[0][i-1]+a[i]$其中$a[i]$表示值为$i$的总和,如果不取这个数的话。。。没什么好说的就是上一次的最大值了

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=1e5+10;
    typedef long long ll;
    
    ll dp[2][mac];//0:不拿,1拿
    ll a[mac];
    
    int main()
    {
        int n;
        scanf ("%d",&n);
        int mx=0;
        for (int i=1; i<=n; i++){
            int x;
            scanf ("%d",&x);
            a[x]+=x;
            mx=max(mx,x);
        }
        for (int i=1; i<=mx; i++){
            dp[0][i]=max(dp[0][i-1],dp[1][i-1]);
            dp[1][i]=dp[0][i-1]+a[i];
        }
        printf ("%lld
    ",max(dp[0][mx],dp[1][mx]));
        return 0;
    }
    View Code

    万能代码

    题目大意:给你一个字符串,问你可以将其压缩的最短长度,压缩规则:M标记重复串的开始,R重复上一个M(如果左边没有M,则从开始算起)到第一个大写字母的解压结果,例如:bcdcdcd可以压缩为bMcdRR,abcabcdabcabcdxyxyz 可以压缩为abcabcdRMxyRz(字符串长度<=50)

    Sample Input 

    bcdcdcdcdxcdcdcdcd

    Sample Output 

    14

     emmm,区间DP,枚举区间长度,枚举起点得终点,枚举当前区间压缩长度,判断是否合理。$dp[i][j]$为第$i$位到第$j$的最短长度,计算在当前合理的情况下的最小长度应该是:$dp[star][ends]=min((star!=1)+len/xlen-1+xlen)$即有len/xlen个重复串,其中len表示区间长度,则有len/xlen-1个R和1个原串有没有M就看起点是否为1了,接下来就是状态转移维护整个区间的最小长度,该区间中每一小段的最小长度是已知的,转移也就是区间合并,即$dp[i][j]=min(dp[i][k]+dp[k+1][j])$

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    char s[55];
    int dp[55][55];
    
    int ok(int st,int ed,int len)
    {
        int sm=ed-st+1;
        if (sm%len) return 0;
        for (int i=len; i<sm; i++){
            int p=i%len;
            if (s[st+p]!=s[st+i]) return false;
        }
        return 1;
    }
    
    int main()
    {
        scanf ("%s",s+1);
        int slen=strlen(s+1);
        for (int i=1; i<=slen; i++)
            dp[i][i]=1;
        for (int len=2; len<=slen; len++){
            for (int star=1; star+len-1<=slen; star++){
                int ends=star+len-1;
                dp[star][ends]=len;
                for (int xlen=2; xlen<=len/2; xlen++){
                    if (ok(star,ends,xlen)){
                        dp[star][ends]=min(dp[star][ends],(star!=1)+len/xlen-1+xlen);
                        //有len/xlen个重复串,则有其-1个R和1个原串
                    }
                }
                for (int tail=star; tail<ends; tail++)
                    dp[star][ends]=min(dp[star][ends],dp[star][tail]+dp[tail+1][ends]);
            }
        }
        printf ("%d
    ",dp[1][slen]);
        return 0;
    }
    View Code

    序列变换

    题目大意:有一个01序列,$S_0=0,S_1=01,S_2=0110...$即$S_k$是$S_{k-1}$的基础上将0替换成01,1替换成10后的结果,让你求$S_n$有多少个00,01,10,11串,对998244353取模

    Sample Input 

    1

    Sample Output 

    0 1 0 0

    emmmm,简单来讲,县找一波规律就会发现$S_k$是$S_{k-1}$的基础上加上将$S_{k-1}$的0替换成1,将1替换成0的结果。

    那么这就很好办了,一眼DP设dp[n][4]分别保存00,01,10,11的数量,我们可以知道,这一次的00是由上一次的11转移过来的,即$dp[i][0]=dp[i-1][0]+dp[i-1][3]$,其他的也是这样互相转换的,但需要注意的是合并的时候由于头一直是0,所以不用考虑,但尾巴一直在变动,所以在$S_k$和$S_k'$合并的时候注意一下中间的是什么就好了。

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=1e5+10;
    typedef long long ll;
    const int mod=998244353;
    
    ll dp[mac][4];//00,01,10,11
    
    int main()
    {
        int n;
        scanf ("%d",&n);
        dp[1][1]=1;
        dp[2][1]=1;dp[2][2]=1;dp[2][3]=1;
        int head=0,tail=0;
        for (int i=3; i<=n; i++){
            dp[i][0]=dp[i-1][0]+dp[i-1][3];
            dp[i][1]=dp[i-1][1]+dp[i-1][2];
            dp[i][2]=dp[i-1][2]+dp[i-1][1];
            dp[i][3]=dp[i-1][3]+dp[i-1][0];
            if (tail==0)dp[i][1]++;
            else dp[i][3]++;
            dp[i][0]%=mod;dp[i][1]%=mod;
            dp[i][2]%=mod;dp[i][3]%=mod;
            tail^=1;
        }
        printf ("%lld %lld %lld %lld
    ",dp[n][0],dp[n][1],dp[n][2],dp[n][3]);
        return 0;
    }
    View Code

    种花

    题目大意:给你n个员工,首尾相接,每个员工的种花量在$l_i,r_i$之间,如果相邻的两个员工的种花量的乘积为质数$p$的倍数,那么老板将奖励每个员工各1000元,问老板发的所有奖励期望对1e9+7取模

    Sample Input 

    3 2
    1 2
    420 421
    420420 420421

    Sample Output 

    4500

    emmmm,差不多是个签到题的压子。我们求出每个区间取得的$p$倍的概率$a_i$,那么两两员工之间得到$p$倍数的概率就是$1-(1-a_i)*(1-a_{i+1})$,我们把这些概率全部加起来,最后乘以2000就完事了

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=1e5+10;
    const int mod=1e9+7;
    typedef long long ll;
    
    ll nb[mac];
    
    ll qick(ll a,ll b)
    {
        ll ans=1;
        while (b){
            if (b&1) ans=ans*a%mod;
            a=a*a%mod;
            b>>=1;
        }
        return ans;
    }
    
    int main()
    {
        int n,p;
        scanf ("%d%d",&n,&p);
        for (int i=1; i<=n; i++){
            ll l,r;
            scanf ("%lld%lld",&l,&r);
            nb[i]=r/p-(l-1)/p;//计算l-r中有几个p的倍数
            nb[i]=nb[i]*qick(r-l+1,mod-2)%mod;//计算取得这些数的概率
        }
        nb[n+1]=nb[1];
        ll ans=0;
        for (int i=1; i<=n; i++){
            ans=ans+(1-(((1-nb[i]+mod)%mod)*((1-nb[i+1]+mod)%mod)%mod)+mod)%mod;
            ans%=mod;
            //1扣去都不行的概率
        }
        printf ("%lld
    ",ans*2000%mod);
        return 0;
    }
    View Code
    路漫漫兮
  • 相关阅读:
    LeetCode Notes_#705_设计哈希集合
    LeetCode Notes_#706_设计哈希映射
    【问题记录】用坚果云同步小书匠数据库发生冲突
    Java设计模式5
    Java设计模式4
    Java设计模式3
    Java设计模式2
    Java设计模式1
    tiantian1412/NTU-HsuanTienLin-MachineLearning
    Jing--Li / book
  • 原文地址:https://www.cnblogs.com/lonely-wind-/p/12555711.html
Copyright © 2011-2022 走看看