简单动态规划(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!

未完待续...



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值