前言
像一個蒟蒻一樣默默地水到第三部分...我果然還是太蒻了
經過一系列調整我們今天來水講數位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!
未完待續...