当前位置: 首页 > news >正文

基于动态规划的潜能觉醒数学模型

文章目录

  • 问题描述
  • 问题分析

问题描述

在游戏《植物大战僵尸 3》的“潜能觉醒”机制中,每个植物都有 5 个不同的潜能,一共有 14 种潜能,如下表所示。

编号效果增幅
1攻击力增加5 - 20
2生命值增加100 - 1000
3受到伤害降低5 - 50
4攻击力增加1% - 20%
5生命值增加1% - 50%
6治疗量增加6% - 15%
7阳光产出间隔降低6% - 15%
8种植冷却降低6% - 15%
9暴击率增加1% - 25%
10暴击伤害增加1% - 50%
11物理伤害增加1% - 20%
12风属性伤害增加1% - 20%
13火属性伤害增加1% - 20%
14电属性伤害增加1% - 20%

其中潜能 3 和 8 的觉醒概率为 18\dfrac1881,其余类型的觉醒概率为 116\dfrac1{16}161。即设第 iii 个潜能觉醒的概率为 pip_ipi,则有 pi={1/8,i=3,81/16,其它p_i=\begin{cases}1/8,&i=3,8\\1/16,&其它\end{cases}pi={1/8,1/16,i=3,8其它

对于某个特定的植物,只有一部分潜能是有效的,例如对于植物“向日葵”,潜能 7 是有效的,潜能 4 是无效的。此外,有效的潜能对于特定的植物,各类有效潜能的重要性也是不同的,例如对于植物“向日葵”,潜能 7 比潜能 5 更重要。为了便于分析,设第 iii 个潜能的满效用为 Wi,0≤Wi≤1W_i,0\le W_i\le1Wi,0Wi1。若觉醒的潜能效果未满,设该潜能的满效果为 mim_imi,此时效果为 xxx,则定义此时的效用 wi(x):=xmiWiw_i(x):=\dfrac x{m_i}W_iwi(x):=mixWi。其中,无效的潜能 Wi=0W_i=0Wi=0max⁡∣S∣=5∑i∈SWi=1\max\limits_{|S|=5}\sum\limits_{i\in S}W_i=1S=5maxiSWi=1 (即最大的 5 个满效用之和为 1) ,则每个植物的潜能方案效用为 ∑i=15wki(xi)\sum\limits_{i=1}^5w_{k_i}(x_i)i=15wki(xi)kik_iki 两两不同。

觉醒潜能需要消耗 2 类资源,潜能药剂和潜能锁定剂,其中前者每次觉醒潜能消耗 1 瓶,后者不是必需的,但如果需要延续原有的 nnn 个潜能,则消耗 3n−13^{n-1}3n1 瓶。这两类资源的获取难度也是不同的,一般认为前者比后者更容易获得。为了便于分析,不妨设潜能药剂的获取成本为 1,潜能锁定剂的获取成本为 ccc。但由于不同方案消耗的资源也是不同的,对此,我们设某个潜能觉醒方案花费的成本为 C(n)=1+⌊3n−1⌋C(n)=1+\lfloor3^{n-1}\rfloorC(n)=1+3n1,其效用期望为 EEE,而不使用锁定剂,只使用潜能药剂随机觉醒 C(n)C(n)C(n) 次的最大效用期望为 E0E_0E0,则我们定义该潜能觉醒方案的价值 v=E/E0v=E/E_0v=E/E0

已知某植物各潜能的满效用为 Wi,i=1,…,14W_i,i=1,\dots,14Wi,i=1,,14,当前的潜能方案为 ki,xi,i=1,…,5k_i,x_i,i=1,\dots,5ki,xi,i=1,,5,其中 kik_iki 表示潜能的种类下标,xix_ixi 表示第 kik_iki 个潜能的效果。现锁定 nnn 个潜能进行潜能觉醒,问选取多大的 nnn,且选取哪几个潜能锁定,能达到潜能方案的价值最高?

问题分析

首先,由于每个植物只有 5 个潜能,一共有 25−1=312^5-1=31251=31 种锁定方案,直接对其暴力遍历即可。而对于每种选择 S={ki:i=1,…,n}S=\{k_i:i=1,\dots,n\}S={ki:i=1,,n},其效用期望

E(S)=Ef(S)+Er(S)=∑i=1nwki+∑i∉Sp^iEr(S∪{i})E(S)=E_f(S)+E_r(S)=\sum_{i=1}^nw_{k_i}+\sum_{i\notin S}\hat p_iE_r(S\cup\{i\}) E(S)=Ef(S)+Er(S)=i=1nwki+i/Sp^iEr(S{i})

其中 p^i=pi1−∑j∈Spj\hat p_i=\dfrac{p_i}{1-\sum\limits_{j\in S}p_j}p^i=1jSpjpi 为新的选择概率。利用递归即可求得效用期望。

值得注意的是,这里并不是锁定效用最高的 nnn 个就是期望最高的,例如只有这 nnn 个潜能的满效用不为零,但这 nnn 个效果都是最低的,显然不固定这些比固定的期望更高。

由概率论的知识可知,nnn 个极大分布的分布函数 Fn(x)F_n(x)Fn(x) 是原分布函数 F(x)F(x)F(x)nnn 次方。而觉醒 1 个潜能是一个由若干均匀分布组成的分布,而觉醒 1 个潜能方案是进行 5 次觉醒 1 个潜能并不重复,即进行 5 次不放回的随机抽样并相加。因此觉醒 1 次分布函数是一个分段线性函数 f(x)f(x)f(x),而觉醒 5 次则是一个分段多项式函数 F(x)F(x)F(x),从而我们可以知道觉醒 nnn 个潜能方案的极大分布的分布函数是 Fn(x)F^n(x)Fn(x)。而其中求 F(x)F(x)F(x) 是复杂的,且容易造成概率消失,即将大量小概率相加会因为计算机精度不足而忽略了小数。因此为了简化问题,在计算分布函数时我们假设 5 个潜能是独立的,从而此时使用 CCC 瓶潜能药剂的效用期望

E0=∫0+∞(1−FC(x))dxE_0=\int_0^{+\infty}(1-F^C(x)){\rm d}x E0=0+(1FC(x))dx

由于在游戏中每个月的潜能觉醒最小任务要求为 50 次,而通过活动每个月能获得 2 瓶锁定剂,因此可假设 c0=50/2=25c_0=50/2=25c0=50/2=25。以熔火猕猴桃为例,其现有潜能为:


则编写程序如下:

#include <stdio.h>
#include <stdint.h>
#include <vector>
#include <set>
#include <algorithm>
#include <utility>
// using std::vector,std::pair;
typedef std::pair<double, double> Pair;
typedef std::vector<Pair> PairVector;
typedef std::vector<double> Vector;
typedef std::set<double> Set;
using std::sort;// 需要输入的数据
uint8_t w[14]{10, // 0: 攻击力增加1, // 1: 生命值增加1, // 2: 受到伤害降低10, // 3: 攻击力增加(%)1, // 4: 生命值增加(%)0, // 5: 治疗量增加0, // 6: 阳光产出间隔降低2, // 7: 种植冷却降低10, // 8: 暴击率增加10, // 9: 暴击伤害增加10, // 10: 物理伤害增加0, // 11: 风属性伤害增加10, // 12: 火属性伤害增加0 // 13: 电属性伤害增加
}; // 效用权重
uint8_t c0 = 25; // 锁定剂单价, 每个月可获得 2 个锁定剂,任务最低要求为 50 次觉醒,即取单价为 25// 固定数据
const uint8_t inf[14]{ 5,100,5,1,1,6,6,6,1,1,1,1,1,1 }; // 效果下界
const uint16_t sup[14]{ 20,1000,50,20,50,15,15,15,25,50,20,20,20,20 }; // 效果上界
const double e0[14]{ 0.625,0.55,0.55,0.525,0.51,0.7,0.7,0.7,0.52,0.51,0.525,0.525,0.525,0.525 }; // 原始期望 e0[i] = (inf[i] + sup[i]) / (2 * sup[i])
const uint8_t exp3[5]{ 0,3,9,27,81 };
uint16_t wSum; // 效用和
PairVector dist; // 不使用锁定剂的分布函数
const char *potStr[14]{ "攻击力增加","生命值增加","受到伤害降低","攻击力增加(%)","生命值增加(%)","治疗量增加","阳光产出间隔降低","种植冷却降低","暴击率增加","暴击伤害增加","物理伤害增加","风属性伤害增加","火属性伤害增加","电属性伤害增加" };inline uint16_t c(uint8_t n) {return 1 + exp3[n] * c0;
}inline double p(uint8_t i) {return i == 2 || i == 7 ? 0.125 : 0.0625;
}// typedef uint16_t IdChoise;// 全局选择方案
struct IdChoise {uint16_t x;IdChoise() :x(0) {}IdChoise(uint16_t a) :x(a) {}void set(uint8_t i) {x |= 1 << i;}IdChoise copySet(uint8_t i) {return IdChoise(x | 1 << i);}bool get(uint8_t i) {return x & 1 << i;}
};// 选择方案
struct Choise {uint8_t x;Choise() :x(0) {}bool operator[](uint8_t i) const {return x & 1 << i;}operator uint8_t() const {return x;}Choise operator++() {x++;return *this;}bool next() {if (x == 31) return false;x++;return true;}uint8_t fixNum() {return __builtin_popcount(x);}const char *print();
};// 潜能方案
struct Scheme {uint8_t id[5];uint16_t effect[5];Scheme(std::initializer_list<uint8_t> ids, std::initializer_list<uint16_t> effects) {// copy_n(ids.begin(),ids.end(),id);// copy_n(effects.begin(),effects.end(),effect);uint8_t i = 0;for (uint8_t t : ids) id[i++] = t;i = 0;for (uint16_t t : effects) effect[i++] = t;}IdChoise getChoise(Choise choise) {IdChoise res;for (uint8_t i = 0; i != 5; i++)if (choise[i])res.set(id[i]);return res;}double calFixedUtility(Choise choise);double getUtility();
};Scheme cur({ 10,5,3,9,2 }, // 潜能编号{ 20,14,20,45,37 } // 潜能效果
);const char *Choise::print() {static char buffer[150];char *p = buffer;bool f = false;for (uint8_t i = 0; i != 5; i++)if (x & 1 << i) {if (f) {*p++ = ',';*p++ = ' ';} elsef = true;const char *s = potStr[cur.id[i]];do *p++ = *s++; while (*s);}*p = '\0';return buffer;
}double Scheme::calFixedUtility(Choise choise) {double r = 0;for (uint8_t i = 0; i != 5; i++)if (choise[i]) {uint8_t t = id[i];r += double(w[t] * effect[i]) / sup[t];}return r;
}double Scheme::getUtility() {uint8_t i = id[0];double r = double(w[i] * effect[0]) / sup[i];for (uint8_t k = 1; k != 5; k++) {i = id[k];r += double(w[i] * effect[k]) / sup[i];}return r / wSum;
}// 计算无锁定效用
double calUtilityOnce() {double res = 0;for (uint8_t i = 0; i != 14; i++) res += (i == 2 || i == 7 ? w[i] << 1 : w[i]) * e0[i];return res / 16;
}// 计算锁定效用
double calUtilityOnce(IdChoise choise) {uint8_t s = 16;double res = 0;for (uint8_t i = 0; i != 14; i++)if (choise.get(i))s -= i == 2 || i == 7 ? 2 : 1;elseres += (i == 2 || i == 7 ? w[i] << 1 : w[i]) * e0[i];return res / s;
}double calUtilityWithoutDiv(IdChoise choise, uint8_t n) {if (n == 1) return calUtilityOnce(choise);uint8_t s = 16;double res = 0;for (uint8_t i = 0; i != 14; i++)if (choise.get(i))s -= i == 2 || i == 7 ? 2 : 1;elseres += (i == 2 || i == 7 ? 2 : 1) * (w[i] * e0[i] + calUtilityWithoutDiv(choise.copySet(i), n - 1));return res / s;
}inline double calUtility(Choise choise) {return (calUtilityWithoutDiv(cur.getChoise(choise), 5 - choise.fixNum()) + cur.calFixedUtility(choise)) / wSum;
}inline double calUtility() {return calUtilityWithoutDiv(IdChoise(), 5) / wSum;
}/*double calUtility() {double res = 0;for (uint8_t i = 0; i != 14; i++) res += (i == 2 || i == 7 ? w[i] << 1 : w[i]) * e0[i];return 5 * res / (wSum << 4);
}double calUtility(IdChoise choise) {uint8_t s = 16;double res = 0;for (uint8_t i = 0; i != 14; i++)if (choise.get(i))s -= i == 2 || i == 7 ? 2 : 1;elseres += (i == 2 || i == 7 ? w[i] << 1 : w[i]) * e0[i];return 5 * res / (s * wSum);
}*/Vector getPoints(double a[], double b[]) {Set points;points.insert(0.);for (uint8_t i = 0; i != 14; i++)if (w[i]) {double t = double(w[i]) / wSum;points.insert(a[i] = double(inf[i]) / sup[i] * t);points.insert(b[i] = t);}Vector u(points.begin(), points.end());sort(u.begin(), u.end());return move(u);
}void getF() {double a[14], b[14];Vector points(getPoints(a, b));for (double x : points) {double y = 0;for (uint8_t i = 0; i != 14; i++)if (w[i]) {double ai = a[i], bi = b[i];if (x > bi)y += p(i);else if (x > ai)y += p(i) * (x - ai) / (bi - ai);} elsey += p(i);dist.push_back(Pair(5 * x, y));}
}// x^e
double pow(double x, uint16_t e) {double r = e & 1 ? x : 1;while (e >>= 1) {x *= x;if (e & 1) r *= x;}return r;
}// x^e - y^e
double subPow(double x, double y, uint16_t e) {double r1, r2;if (e & 1) {r1 = x;r2 = y;} elser1 = r2 = 1;while (e >>= 1) {x *= x;y *= y;if (e & 1) {r1 *= x;r2 *= y;}}return r1 - r2;
}inline double integrate(const Pair &p1, const Pair &p2, uint16_t n) {return abs(p1.second - p2.second) < 1e-6 ? pow(p1.second, n) * (p2.first - p1.first) : subPow(p2.second, p1.second, n + 1) * (p2.first - p1.first) / (p2.second - p1.second) / (n + 1);
}double calUtility(uint16_t c) {uint8_t n = dist.size() - 1;double r = dist[n].first;for (uint8_t i = 0; i < n; i++) r -= integrate(dist[i], dist[i + 1], c);return r;
}void getWSum() {uint8_t a[14];for (uint8_t i = 0; i != 14; i++) a[i] = w[i];sort(a, a + 14, std::greater<int>());wSum = a[0] + a[1] + a[2] + a[3] + a[4];
}int main() {getWSum();getF();Choise choise, bestChoise[5];double bestUtility[5]{ 0 };printf("原始效用: %f.\n", cur.getUtility());printf("不使用锁定剂效用期望: %f, 价值为 1.\n", calUtility());while (choise.next()) {uint8_t n = choise.fixNum();double e = calUtility(choise);if (e > bestUtility[n]) {bestUtility[n] = e;bestChoise[n] = choise;}}for (uint8_t i = 1; i != 5; i++) printf("使用 %u 个锁定剂的最优效用期望: %f, 锁定的潜能为 %s, 价值为 %f.\n", i, bestUtility[i], bestChoise[i].print(), bestUtility[i] / calUtility(c(i)));return 0;
}

运行得结果:

原始效用: 0.594800.
不使用锁定剂效用期望: 0.238230, 价值为 1.
使用 1 个锁定剂的最优效用期望: 0.373869, 锁定的潜能为 物理伤害增加, 价值为 0.373869.
使用 2 个锁定剂的最优效用期望: 0.516153, 锁定的潜能为 物理伤害增加, 攻击力增加(%), 价值为 0.516153.
使用 3 个锁定剂的最优效用期望: 0.647065, 锁定的潜能为 物理伤害增加, 攻击力增加(%), 暴击伤害增加, 价值为 0.647065.
使用 4 个锁定剂的最优效用期望: 0.632182, 锁定的潜能为 物理伤害增加, 攻击力增加(%), 暴击伤害增加, 受到伤害降低, 价值为 0.632182.

从而可知在此现有潜能下,最优的觉醒潜能方案是不使用锁定剂。

http://www.dtcms.com/a/594015.html

相关文章:

  • 中文网站建设和英文网站建设的区别微信公众平台绑定网站
  • 百度站长网站文件验证公司基本介绍模版
  • iis 网站打不开如何做好一个外贸进网站的编辑
  • next.js学习——react入门
  • Java【缓存设计】定时任务+分布式锁实战:Redis vs Redisson实现状态自动扭转以及全量刷新预热机制
  • 缓存更新策略
  • 网站海外推广方案品牌策划公司的市场
  • 大潮建设集团有限公司 网站h5的制作步骤
  • 河中跳房子(信息学奥赛一本通- P1247)
  • Julia 日期和时间
  • 虚幻引擎5 GAS开发俯视角RPG游戏 P07-11 实现自动运行
  • 培训人员网站建设龙岗网站开发公司
  • 【经验分享】Genio 520/Genio720未使用引脚处理方法
  • 重庆免费网站建站模板微网站趋势
  • DDR5引领变革:内存条行业的技术迭代与市场重构
  • 一小时速通Pytorch之Tensor张量(一)
  • 怎么做网站教程 用的工具提供网站推广公司电话
  • 网站服务内容怎么写wordpress 响应时间
  • 主成分分析(Principal Component Analysis,PCA)的个人理解
  • sqlite数据库迁移至mysql
  • PostgreSQL 高并发优化:从 “连接数满到崩” 到 “支撑 10 万 QPS” 的实战技巧
  • 怎么免费建自己的网站网络营销课程培训机构
  • 团队开发者git仓库工作手册
  • 欧美风网站建设seo品牌优化整站优化
  • 2.8 模型压缩与优化技术
  • 数字孪生工厂浪潮来袭,众趣科技以实景智能筑牢智造基石
  • [設計模式]二十三種設計模式
  • 有视频接口怎么做网站哪个行业最需要做网站
  • 解锁AI工作流的终极密码:Semantic Kernel Process框架深度技术解析
  • 从0到1:Makefile自动化编译实战全解析