t 种蚂蚁,共a只,同种蚂蚁不区分,构成各种大小的不同多重集,问大小在闭区间[s,b]的多重集共有多少个

多重集的意思是元素可重复,构成元素或重数不同就是不同的多重集,比如{1,1,2,3}是一个多重集,这里的元素是蚂蚁的种类编号

这个题吧,其实有很多槽点

博主做这道题自认为做的很完美,但是WA了一个小时,完全找不到错误

后来一看,要求模1000000,没看到。。。

然后改了后又WA,立马意识到中间其实会爆int,过程中也要取模,不能最后求结果的时候只取一次模

然后第二个槽点是什么呢

这个题给的数据说 t 是 1e3 ,b<=a 是 1e5

我们平常写的朴素的算法的话,复杂度是 O(t*b^2) 是 1e13...

看这个说明肯定超时而且超的不止一点点...

于是博主就花时间想如何优化,然后绞尽脑汁只能优化成 O(t*b) 是 1e8...

然鹅前者就能过,这数据在搞神魔......

不嗦废话了...开始讲解...

法一:朴素O(t*b^2)

dp[i][j]表示前i种蚂蚁,取j个时的不同构成数

那么如何转移呢,特别简单

举个例子,之前是 {1} {2} {1,2} 这三个集合 dp[2][1]=2 , dp[2][2]=1

进来2个3,那么比如求dp[3][3],就是一个的添上2个3,两个的添上一个3

dp[3][3]=dp[2][1]+dp[2][2]

你理解一下,就是说,转移方程是

利用背包的思想如果 j 从大到小更新,第一维就可以去掉

然后下面是代码

#include <cstdio>
#include <algorithm>
#define M 1000000
using namespace std;
int t,a,s,b,dp[100005]={1},x[1005],id,ans;
int main(){scanf("%d%d%d%d",&t,&a,&s,&b);while(a--) scanf("%d",&id),x[id]++;for(int i=1;i<=t;i++) for(int j=b;j>=1;j--) for(int k=max(0,j-x[i]);k<=j-1;k++) dp[j]=(dp[j]+dp[k])%M;for(int i=s;i<=b;i++) ans=(ans+dp[i])%M;printf("%d\n",ans);return 0;
}

法二:利用本轮优化递推O(t*b)

如果我们按 j 从小到大顺序做 dp[i][j]

那么你可能会想,我可不可以用 dp[i][j-1] 更新 dp[i][j] 呢

dp[i][j-1] 讲道理再添一个数 就是 dp[i][j] 了

但是呢,你会发现他可能添不了,更新 dp[i][j-1] 包含正好用掉 cnt[i] 个的情况,那就没东西添了

然而你又发现,不就这一种情况吗。。无脑减掉就好了

于是转移方程如下

这时候因为顺序更新就不能降维了

而第一维 1e4 第二维 1e5 将导致 MLE,很简单的常用手段,第一维%2即可

那么代码如下

#include <cstdio>
#include <algorithm>
#define M 1000000
using namespace std;
int t,a,s,b,dp[2][100005],x[1005],id,ans;
int main(){scanf("%d%d%d%d",&t,&a,&s,&b);while(a--) scanf("%d",&id),x[id]++;dp[0][0]=dp[1][0]=1;for(int i=1;i<=t;i++) for(int j=1;j<=b;j++){dp[i%2][j]=(dp[(i-1)%2][j]+dp[i%2][j-1])%M;if(j-x[i]>=0) dp[i%2][j]=(dp[i%2][j]-dp[(i-1)%2][j-1-x[i]]+M)%M;}for(int i=s;i<=b;i++) ans=(ans+dp[t%2][i])%M;printf("%d\n",ans);return 0;
}

然后的话,上面这两种写法也是可以继续优化的

这里,不用每次都跑到b

因为后面全是0嘛,每次你记录 m+=x[i],然后跑到 min(m,b) 就行

然后的话下面还有个方法,好像目前没看到有人这么写的,我不具体讲,感兴趣的话可以看一下

和上面那个复杂度一样,但是这个dp记录的是直接是上面的dp的前缀和,写起来略微麻烦一点点

法三:直接前缀和dp(不具体讲了)O(t*b)

#include <cstdio>
#include <algorithm>
#define M 1000000
using namespace std;
int t,a,s,b,x[1005],dp[2][100005],id,am;
int main(){scanf("%d%d%d%d",&t,&a,&s,&b);while(a--) scanf("%d",&id),x[id]++;for(int i=1;i<=t;i++){int tmp=am+x[i],inc=0;for(int j=1;j<=min(tmp,b);j++){int st=max(0,j-x[i]-1),en=min(am,j-1);inc=(inc+dp[(i-1)%2][en]-dp[(i-1)%2][st]+M)%M;if(j-x[i]<=0) inc=(inc+1)%M;if(j>am) dp[i%2][j]=(dp[(i-1)%2][am]+dp[(i-1)%2][j]+inc)%M;else dp[i%2][j]=(dp[(i-1)%2][j]+inc)%M;}am=tmp;}printf("%d\n",(dp[t%2][b]-dp[t%2][s-1]+M)%M);return 0;
}

这个是我一开始的想法和写法,因为担心超时,所以没有分开写前缀和和dp值

事实上因为数据很水,就算时间复杂度翻成二倍也没事

但是上面这个思路确实很不错,毕竟是直接记录前缀和的dp直接更新,很厉害

下面这个比较正常的分开写,我懒得写了,直接上一个别人的代码

法四:记录dp前缀和优化dp更新O(t*b)

那么,这些代码其实都大同小异

但是很有学习价值

前缀和优化是很常用也很厉害的一种手段,这个要会

法二那个本轮转移也很强,想法很不错

这道题我觉得有必要好好掌握法二和法四

POJ - 3046 多重集组合数问题的线性DP(四种方法)相关推荐

  1. Java解决Hash(散列)冲突的四种方法--开放地址法(线性探测,二次探测,伪随机探测)、链地址法、再哈希、建立公共溢出区

    Java解决Hash(散列)冲突的四种方法--开放地址法(线性探测,二次探测,伪随机探测).链地址法.再哈希.建立公共溢出区 参考文章: (1)Java解决Hash(散列)冲突的四种方法--开放地址法 ...

  2. 显示器接口_如何选择显示器连接线?VGA、DVI、HDMI、DP四种主流接口知识

    一般来说,显示器选择哪一种连接线是显卡显示接口与显示器显示接口决定的,也就是说,如果您的显示器或者显卡上其中一个没有DP接口,那么是无法使用DP接口的,当然如果显示器和显卡同时有DP.HDMI.DVI ...

  3. dp怎么接显示器和主机_如何选择显示器连接线?VGA、DVI、HDMI、DP四种主流接口知识...

    一般来说,显示器选择哪一种连接线是显卡显示接口与显示器显示接口决定的,也就是说,如果您的显示器或者显卡上其中一个没有DP接口,那么是无法使用DP接口的,当然如果显示器和显卡同时有DP.HDMI.DVI ...

  4. dp线长什么样子_主机dvi接口是什么样子(VGA、DVI、HDMI、DP四种主流接口知识)...

    一般来说,显示器选择哪一种连接线是显卡显示接口与显示器显示接口决定的,也就是说,如果您的显示器或者显卡上其中一个没有DP接口,那么是无法使用DP接口的 当然如果显示器和显卡同时有DP.HDMI.DVI ...

  5. t40服务器连接显示器的接口,如何选择显示器连接线?VGA、DVI、HDMI、DP四种主流接口知识...

    一般来说,显示器选择哪一种连接线是显卡显示接口与显示器显示接口决定的,也就是说,如果您的显示器或者显卡上其中一个没有DP接口,那么是无法使用DP接口的,当然如果显示器和显卡同时有DP.HDMI.DVI ...

  6. 组合数c(n,m)计算的四种方法

    转载自 组合c(m,n)的计算方法    问题:求解组合数C(n,m),即从n个相同物品中取出m个的方案数,由于结果可能非常大,对结果模10007即可. 共四种方案.ps:注意使用限制. 方案1: 暴 ...

  7. 组合数C(n,m)的四种计算方法

    转载自 组合c(m,n)的计算方法    问题:求解组合数C(n,m),即从n个相同物品中取出m个的方案数,由于结果可能非常大,对结果模10007即可. 共四种方案.ps:注意使用限制. 方案1: 暴 ...

  8. POJ - 1958 Strange Towers of Hanoi(线性dp)

    题目链接:点击查看 题目大意:继承经典的n个盘子三座塔的汉诺塔问题,现在问对于n个盘子四座塔的升级版汉诺塔问题,对于n=1~12的答案分别是多少 题目分析:首先分析三座塔的情况,对于第n个盘子而言,我 ...

  9. POJ 1187 陨石的秘密 (线性DP)

    题意: 公元11380年,一颗巨大的陨石坠落在南极.于是,灾难降临了,地球上出现了一系列反常的现象.当人们焦急万分的时候,一支中国科学家组成的南极考察队赶到了出事地点.经过一番侦察,科学家们发现陨石上 ...

最新文章

  1. Set 的合集 并集 差集
  2. 你知道你常用的dos和linux命令吗?
  3. 【OS学习笔记】十二 现代处理器的结构和特点
  4. 体积最小桌面linux,Tiny Core Linux - 体积最小的精简 Linux 操作系统发行版之一 (仅10多MB) - 蓝月网络...
  5. ROS中阶笔记(十):ROS机器人综合应用
  6. Python机器学习工具箱
  7. Microsoft Office/visio/Project 2013 with SP1 简体中文零售版
  8. idea代码自动格式化配置
  9. 【智能车】模糊PID控制原理详解与代码实现
  10. python人口普查数据显示_如何使用FCC的API在Python中查找人口普查数据块并遍历dict列表?...
  11. CCD实际尺寸对照表
  12. linux系统读移动硬盘,在linux系统上识别与挂载移动硬盘数据
  13. 《失业七个月,面试六十家公司》的深圳体验
  14. 利用华硕路由器实现创维电视广告屏蔽
  15. 新课发布-SpringBoot2.0缓存中间件Redis技术入门与实战(抢红包系统设计与实战)
  16. 使用BlueZ连接蓝牙手柄
  17. 数字孪生在能源、电力系统、电厂行业的应用实例
  18. 最新小米易支付系统源码
  19. 虾扑 erp 有什么用?做 虾皮 如何快速上货?
  20. Perfetto —— 靠谱的trace利器

热门文章

  1. JavaScript 基础(三) - Date对象,RegExp对象,Math对象,Window 对象,History 对象,Location 对象,DOM 节点...
  2. nova 宿主机重启自动恢复虚拟机运行状态
  3. Linux修改挂载目录名称
  4. supervisor-3:xml_rpc
  5. 更改AngularJS的语法解析符号
  6. 配置VS2008本地调试.NETFRAMEWORK源代码
  7. 个人永久性免费-Excel催化剂插件功能修复与更新汇总篇之七
  8. Crontab- Linux必学的60个命令
  9. vue框架的vue-router路由的运用
  10. 正则表达式去除括号的问题