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; }
摸鱼的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; }
万能代码
题目大意:给你一个字符串,问你可以将其压缩的最短长度,压缩规则: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; }
序列变换
题目大意:有一个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; }
种花
题目大意:给你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; }