NOIP2023模拟赛 种树

1|0动态规划思路

1|1可行性

抛开取模问题和空间限制,该题存在最优子结构性和无后效性,而这两个问题都可以另外处理。

1|2状态定义

Fi,j 为前 i 棵树,剩余 j 单位化肥时的最大覆盖距离。

1|3状态转移

1|0先忽略取模问题

1|0推导状态转移方程

1|0不选择对当前第 i 个树木施肥。

显而易见,Fi,j 应该从前 i1 棵树,剩余 j 单位化肥的状态再乘以当前的高度因子数转移而来。

Fi,j=Fi1,j×getInShu(pi)

1|0选择对当前第 i 个树木施肥。

 Fi,j 应该从前 i1 棵树,剩余 j×k 单位化肥转移过来。

这里的 j×k 必须满足两个条件:

  • j×kw
    • 显然必须小于总化肥量。
  • j×k w 的正因数。
    • 从题干中(要求  k  必须为当前化肥量的正因数)推出,所有状态的当前化肥量显然只能是总化肥量  w  的正因数。

由此可以推导出,j×k 就是 w 的正因子。

预处理出来 w 的所有正因子存入 fertilizer  数组中,并找出大于当前剩余 j 单位化肥且当前剩余 j 单位化肥是其正因子的 fertilizeri,即为待转移状态的剩余单位化肥量,此处设为 j

Fi,j=max(Fi1,j,Fi1,j+getInShu(p[i]×(j÷j))

1|0代码实现

1|0首先初始化初状态
F[0][w] = 1;

即剩下 w 化肥量(还没用过化肥时)的初状态。

1|0状态转移实现
for (int i = 1; i <= n; i++) { for (int j = 1; j <= nfy; j++) { F[i][fertilizer[j]] = F[i - 1][fertilizer[j]] * getInShu(p[i]); for (int k = j + 1; k <= nfy; k++) { if (fertilizer[k] % fertilizer[j] == 0)//判断当前数是否是w正因子的正因子 { ll temp = fertilizer[k] / fertilizer[j];//temp剩余的化肥量除的数 F[i][fertilizer[j]] = max(F[i - 1][fertilizer[k]] * getInShu(p[i] * temp), F[i][fertilizer[j]]); } } } }

到此,我们算是在无视取模和空间限制的情况下完成了状态转移。

1|4快速求因子数

1|0问题来由

用动态规划实现复杂度接近于 O(n2),此时再用平常的 O(n) 算法来求因子数会导致超时。

1|0处理方法

1|0依靠两个结论

  • 一个正整数一定能分解为若干质数 n 次方的乘积。
  • 一个数的因子数等于该数分解为若干质数 n 次方的乘积后每个质数的次方数加一的乘积。

1|0举个例子

120=23×3×5

那么因子数则是 (3+1)×(1+1)×(1+1)=16

1|0代码实现

ll getInShu(ll n) { ll ans = 1; for (int i = 2; i * i <= n; i++) { if (n % i == 0)//质因数分解 { ll cnt = 0;//即这个因数的次方 while(n % i == 0) { ++cnt; n /= i; } ans = (ans mod) * ((cnt + 1) mod); ans = ans mod; } } if (n != 1)//说明剩下最后一个质因数 { ans *= 2; } return ans mod; }

1|5处理取模问题

1|0问题来由

最优性问题每次都要判断大小,而取模判断大小很可能出现各种问题。

比如假设对 1000 取模,比较 99  1001 的大小,显然两数取模 99>1,出现了问题。

而动态规划时每次判断最优解就会被这个问题影响正确性。

1|0处理方法

仅针对本题,取模的对象是通过乘法进行状态转移的,那么问题就转移到了如何正确的在状态转移时判断两个状态的大小

除了习惯性的直接比较两个状态的大小外,可以利用乘法的性质,即两数相乘正比于两数对数相加

x×ylgx+lgy

如此,我们维护一个数组 DPDPF 数组同步操作,只是所有的相乘改为取对数相加。显然对数的数量级不会越界,不需要取模,也保证了正确性。

代码实现非常容易,即所有的相乘操作都换成对数相加。

初始化加入:

dp[0][w] = log(1);

不选择对当前第 i 个树木施肥。

dp[i][fertilizer[j]] = dp[i - 1][fertilizer[j]] + log(getInShu(p[i]));

选择对当前第 i 个树木施肥。

dp[i][fertilizer[j]] = dp[i - 1][fertilizer[k]] + log(getInShu(p[i] * temp));

1|6优化空间

1|0问题来由

本题的 N  W 都到了 104  的大小,这意味着如果使用二维数组,需要开 108 longdouble

这导致了 MLE

1|0处理方法

1|0MAP 思路

从状态转移方程的推导不难看出,因为剩余化肥数只能从 w 的所有正因子中选(一个数的正因子数肯定小于这个数本身,数越大越明显),而第二维却开了 w 个空间,即用来表示剩余化肥数的维度是存在大量浪费的。

有什么办法能解决呢,蒟蒻我觉得最好实现的就是用 map 来维护两个动态规划数组。

map 本质上也是个容器,但是它只有用到了才会开辟空间,即放入这个元素到 map 容器内。

这真正做到了没用到的就不开辟空间。

1|0实现

即维护一个 map<pair<int, int>, int or double>

pair 用来替代动规数组的两维,而 intdouble 用来存动规数组的值。

代码非常简单,首先开辟两个动规数组。

map<pair<int, int>, long long> F; map<pair<int, int>, double> dp;

之后把原来代码所有的下标改为 pair 即可。

F[i][j] = k -> F[{i, j}] = k;

1|0滚动数组思路

经 @User439000 提醒,我发现这题也完全可以用滚动数组优化。

由于对 fi,j 有影响的只有 fi1,j,可以去掉第一维,直接用第二维来表示状态转移。

Fj=max(Fj×getInShu(pi),Fj+getInShu(p[i]×(j÷j))

1|0实现

首先开两个一维数组来代替 Fdp

由于滚动数组的更新仍需要从上一个状态开始,而本动态规划算法的状态更新是每轮从剩余肥料大到小更新的。因此第二层枚举当前肥料剩余量的循环应从小到大,这样可以保证每次更新时使用的是上一次更新的状态。

for (int i = 1; i <= n; i++) { for (int j = 1; j <= nfy - 1; j++) { dp[fy[j]] = dp[fy[j]] + log(getInShu(p[i])); F[fy[j]] = (F[fy[j]] mod) * (getInShu(p[i]) mod); F[fy[j]] = F[fy[j]] mod; for (int k = nfy; k >= j + 1; k--) { if (fy[k] % fy[j] == 0) { ll temp = fy[k] / fy[j]; if (dp[fy[j]] < dp[fy[k]] + log(getInShu(p[i] * temp))) { dp[fy[j]] = dp[fy[k]] + log(getInShu(p[i] * temp)); F[fy[j]] = ((F[fy[k]]mod) * (getInShu(p[i] * temp)mod))mod; } } } } }

1|7代码实现

1|0map 优化内存实现

#include<iostream> #include<algorithm> #include<cstdio> #include<cmath> #include<map> #define mod %998244353 #define ll long long const int N = 1e4 + 10; using namespace std; int n, w; int p[N]; int fertilizer[N], nfy; ll ans = -1; map<pair<int, int>, long long> F; map<pair<int, int>, double> dp; ll getInShu(ll n) { ll ans = 1; for (int i = 2; i * i <= n; i++) { if (n % i == 0) { ll cnt = 0; while(n % i == 0) { ++cnt; n /= i; } ans = (ans mod) * ((cnt + 1) mod); ans = ans mod; } } if (n != 1) { ans *= 2; } return ans mod; } void setFertilizer() { for (int i = 1; i <= w; i++) { if (w % i == 0) { fertilizer[++nfy] = i; } } } void solveIfWEqualOne() { ll ans = 1; for (int i = 1; i <= n; i++) { cin >> p[i]; ans = ans * getInShu(p[i])mod; ans = ans mod; } cout << ans mod; return; } int main() { cin >> n >> w; if (w == 1)//即无视了化肥问题,显然直接特判比动规快多了 { solveIfWEqualOne(); return 0; } for (int i = 1; i <= n; i++) { cin >> p[i]; } setFertilizer(); F[{0,w}] = 1; dp[{0,w}] = log(1); for (int i = 1; i <= n; i++) { for (int j = nfy - 1; j >= 1; j--) { dp[{i,fertilizer[j]}] = dp[{i - 1,fertilizer[j]}] + log(getInShu(p[i])); F[{i,fertilizer[j]}] = (F[{i - 1,fertilizer[j]}] mod) * (getInShu(p[i]) mod); F[{i,fertilizer[j]}] = F[{i,fertilizer[j]}] mod; for (int k = nfy; k >= j + 1; k--) { if (fertilizer[k] % fertilizer[j] == 0) { ll temp = fertilizer[k] / fertilizer[j]; if (dp[{i,fertilizer[j]}] < dp[{i - 1,fertilizer[k]}] + log(getInShu(p[i] * temp))) { dp[{i,fertilizer[j]}] = dp[{i - 1,fertilizer[k]}] + log(getInShu(p[i] * temp)); F[{i,fertilizer[j]}] = ((F[{i - 1,fertilizer[k]}]mod) * (getInShu(p[i] * temp)mod))mod; } } } } } cout << F[{n, 1}]mod; return 0; }

1|0滚动数组优化内存实现

#include <iostream> #include <algorithm> #include <cstdio> #include <cmath> #define mod %998244353 #define ll long long const int N = 1e4 + 10; using namespace std; int n, w; int p[N]; int Fertilizer[N], nfy; ll F[N]; float dp[N]; ll getInShu(ll n) { ll ans = 1; for (int i = 2; i * i <= n; i++) { if (n % i == 0) { ll cnt = 0; while (n % i == 0) { ++cnt; n /= i; } ans = (ans mod) * ((cnt + 1) mod); ans = ans mod; } } if (n != 1) { ans *= 2; } return ans mod; } void setFertilizer() { for (int i = 1; i <= w; i++) { if (w % i == 0) { Fertilizer[++nfy] = i; } } } void solveIfWEqualOne() { ll ans = 1; for (int i = 1; i <= n; i++) { cin >> p[i]; ans = ans * getInShu(p[i])mod; ans = ans mod; } cout << ans mod; return; } int main() { cin >> n >> w; if (w == 1) { solveIfWEqualOne(); return 0; } for (int i = 1; i <= n; i++) { cin >> p[i]; } setFertilizer(); F[w] = 1; dp[w] = log(1); for (int i = 1; i <= n; i++) { for (int j = 1; j <= nfy - 1; j++) { dp[Fertilizer[j]] = dp[Fertilizer[j]] + log(getInShu(p[i])); F[Fertilizer[j]] = (F[Fertilizer[j]] mod) * (getInShu(p[i]) mod); F[Fertilizer[j]] = F[Fertilizer[j]] mod; for (int k = nfy; k >= j + 1; k--) { if (Fertilizer[k] % Fertilizer[j] == 0) { ll temp = Fertilizer[k] / Fertilizer[j]; if (dp[Fertilizer[j]] < dp[Fertilizer[k]] + log(getInShu(p[i] * temp))) { dp[Fertilizer[j]] = dp[Fertilizer[k]] + log(getInShu(p[i] * temp)); F[Fertilizer[j]] = ((F[Fertilizer[k]]mod) * (getInShu(p[i] * temp)mod))mod; } } } } } cout << F[1]mod; return 0; }

1|8结语

昨天模拟赛想了三个小时没想出来处理取模问题心态爆炸,觉得自己白想了,自己方法不可行,气的晚上睡不着。

到今天终于把这个做法完善通过,很喜悦。

很多时候路都是走出来的。

十一月十二号二十点整于旗山实验室。


__EOF__

本文作者Kdlyh
本文链接https://www.cnblogs.com/kdlyh/p/17826185.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   加固文明幻景  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示