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?'\n':' ');
    }
    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\n",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\n",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\n",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\n",ans*2000%mod);
    return 0;
}
View Code
posted @ 2020-04-05 08:40  lonely_wind  阅读(308)  评论(1编辑  收藏  举报