前言
像一个蒟蒻一样默默地水到第三部分...我果然还是太蒻了
经过一系列调整我们今天来水讲数位DP与概率DP
数位DP
数位DP相比直接爆搜的优越性在于:它将当前位的情况直接汇总了,且对之前位的要求大幅减少
所以我们直接上习题
(1)windy数(SCOI2009)
题面见链接http://www.lydsy.com/JudgeOnline/problem.php?id=1026
对于每个相邻数位稍微加个限制即可
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#define int long long
using namespace std;
inline int read(){
int i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
inline void write(int x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
int f[15][10][2],sze,a,b,lim[15];
int dp(int x){
memset(f,0,sizeof(f));
int tmp=x;sze=0;
while(tmp){
lim[++sze]=tmp%10;
tmp/=10;
}
for(int i=0;i<=9;++i)
if(i<=lim[1]) ++f[1][i][0];
else ++f[1][i][1];
for(int i=2;i<=sze;++i)
for(int j=0;j<=9;++j)
for(int k=0;k<=9;++k)
if(abs(j-k)>=2){
if(j<lim[i])
f[i][j][0]+=f[i-1][k][1]+f[i-1][k][0];
else if(j==lim[i]){
f[i][j][0]+=f[i-1][k][0];
f[i][j][1]+=f[i-1][k][1];
}else
f[i][j][1]+=f[i-1][k][1]+f[i-1][k][0];
}
int ret=0;
for(int i=1;i<lim[sze];++i)
ret+=f[sze][i][1]+f[sze][i][0];
ret+=f[sze][lim[sze]][0];
for(int i=sze-1;i;--i)
for(int j=1;j<=9;++j)
ret+=f[i][j][1]+f[i][j][0];
return ret;
}
signed main(){
a=read();b=read();
if(a!=1)
write(dp(b)-dp(a-1));
else
write(dp(b));
return 0;
}
(2)B-number(HDU3652)
题面见链接http://acm.hdu.edu.cn/showproblem.php?pid=3652
我们只需要在第一题思路的基础上维护一个取模的余数即可。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#define int long long
using namespace std;
inline int read(){
int i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
inline void write(int x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
#define stan 22
int l,r,f[stan][stan][3],ans;
int a[stan],n;
void build(int x){
n=0;
while(x) a[++n]=x%10,x/=10;
return;
}
int dfs(int length,int kind,int mod,bool disable){
if(length==0) return kind==2&&mod==0;
if(disable&&f[length][mod][kind]!=-1)
return f[length][mod][kind];
int ret=0,end=disable?9:a[length];
for(int i=0;i<=end;++i){
int ismod=(mod*10+i)%13;
if(kind==2||kind==1&&i==3) ret+=dfs(length-1,2,ismod,disable|(i<end));
else if(i==1) ret+=dfs(length-1,1,ismod,disable|(i<end));
else ret+=dfs(length-1,0,ismod,disable|(i<end));
}
if(disable) f[length][mod][kind]=ret;
return ret;
}
signed main(){
memset(f,-1,sizeof(f));
while(scanf("%d",&r)!=EOF){
build(r);
ans=dfs(n,0,0,0);
write(ans);puts("");
}
return 0;
}
长期被扔到数学分类里的概率/期望DP
其他5个基本类型的DP,是可以完全不讲前因后果的。(因为dalao们一眼就会QAQ)
但唯独概期望DP是要单独提出来批斗一番的。
首先我们把期望的定义拖出来:
“在概率论和统计学中,数学期望(mean)(或均值,亦简称期望)是试验中每次可能结果的概率乘以其结果的总和,是最基本的数学特征之一。它反映随机变量平均取值的大小。”——(摘自百度百科)
然后对于期望的感性认知,我们可以通过百科词条“历史故事”一栏进行直观感受。
(这里附送一个传送门)
所以我们现在可以从一些简单的习题入手了
(1)Red is good(BZOJ1419)
题面依旧见链接...欸等一等为什么是权限题
好吧我吧题意口胡一遍:有R张红牌与B张黑牌
随机选择一张,如果为红牌,获得一点分数,如果为黑牌,减少一点分数。
可以随时停止选择
求在最优策略下平均能得到多少钱
————————————————————————————————————————————————
吼的这是一道期望DP
期望DP常见的套路是倒推。(不要问我为什么因为我也不知道)
我们设定f[i][j]表示还剩余i张红牌与j张黑牌的情况下,赚得更多钱的期望
很明显f[0][0]==0(因为已经无牌可拿)
对于f[i][j],其期望为max(0,(f[i-1][j]+1)*(i/(j+i))+(f[i][j-1]+1)*(j/(j+i))
最后输出f[R][B]即可
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline int read(){
int i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
inline void write(int x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
#define stan 5555
int r,b,k;
long long ans;
double f[2][stan];
signed main(){
r=read();b=read();
for(int i=0;i<=r;++i,k=i&1,f[k][0]=i){
for(int j=1;j<=b;++j)
f[k][j]=max((double)0,(double)(i)*1.0/(i+j)*(f[k^1][j]+1)+(double)(j)*1.0/(i+j)*(f[k][j-1]-1));
}
ans=f[r&1][b]*1000000;
printf("%lf",ans/1000000.0);
return 0;
}
(2)收集邮票(BZOJ1426)
怎么又是权限题...难道B站觉得单纯期望题是全站级保护题目?
题面:有n种邮票,每次可以买1张,买到每一种的概率都是1/n,买的第k张邮票的花费是k。求买齐n种邮票的花费期望
————————————————————————————————————————————————
吼的这还是一道期望DP
我们设num[i]为买齐了i种后集齐所有SSR邮票的期望张数
很显然num[n]==0
对于num[i],有num[i]=i/n*(num[i]+1)+(n-i)/n*(num[i+1]+1)
很显然我们把这个式子化一化就又是一个倒推式了
然后我们设f[i]为买齐i种后期望花费的钱数
可以视为这张邮票花费为1,其余邮票上涨1元
也就是f[i]=(n-i)/n*(f[i+1]+num[i+1]+1)+i/n*(f[i]+num[i]+1);
然后又是一阵血雨腥风的化简
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#define int long long
#define mod 10007
using namespace std;
inline int read(){
int i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
inline void write(int x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
#define stan 11111
double n,num[stan],f[stan];
signed main(){
n=read();
for(int i=n-1;i>=0;--i){
num[i]=num[i+1]+n/(n-i);
f[i]=f[i+1]+num[i+1]+num[i]*i/(n-i)+n/(n-i);
}
printf("%.2lf",f[0]);
return 0;
}
(3)collecting bugs(POJ2096)
题面见链接:http://poj.org/problem?id=2096
嗯,虽然是英文的但总比没有好
还是再复述一下题意吧:有n个子系统,有s种bug
每次能在1个子系统中找到1个bug
求在n个子系统中找齐总共s种bug的期望
————————————————————————————————————————————————
吼的这依旧是一道期望DP
对于每一个状态f[i][j]表示已经在i个子系统中总共收集了j种bug的期望
很显然f[n][s]还是等于0的
对于每一个f[i][j]都有f[i][j]=(i/n*j/s)*(f[i][j]+1)+((n-i)/n*j/s)*(f[i+1][j]+1)+(i/n*(s-j)/s)*(f[i][j+1]+1)+((n-i)/n*(s-j)/s)*(f[i+1][j+1]+1)
然后还是一次腥风血雨的化简
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline int read(){
int i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
inline void write(int x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
#define stan 1111
int n,s;
double f[stan][stan];
signed main(){
n=read();s=read();
for(int i=n;i>=0;--i)
for(int j=s;j>=0;--j)
if(i!=n||j!=s)
f[i][j]=((n-i)*j*f[i+1][j]+i*(s-j)*f[i][j+1]+(n-i)*(s-j)*f[i+1][j+1]+n*s)/(n*s-i*j);
printf("%.4f",f[0][0]);
return 0;
}
所以期望概率DP就这么easy?Naive!
未完待续...