簡單動態規劃(3)——從入門到放棄

前言

像一個蒟蒻一樣默默地到第三部分...我果然還是太蒻了

經過一系列調整我們今天來講數位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!

未完待續...



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章