整理的算法模板合集: ACM模板


目录

  • 同余最短路
  • 例题1:luogu P3403 跳楼机
  • 例题2:luogu P2371 [国家集训队]墨墨的等式
  • 例题3:luogu P2662 牛场围栏

同余最短路

同余最短路实际上使用最短路模型来优化DP问题,使用同余模型来优化极大的数据范围。
基本思想:通过同余构造某些状态,状态之间的关系类似于两点之间的带权有向边。


通常是解决给定m个整数,求这m个整数能拼凑出多少的其他整数(这m个整数可以重复取)或给定m个整数,求这m个整数不能拼凑出的最小(最大)的整数。

我们首先考虑一个问题:

给你一个数M,三个数x,y,z,问111 ~ MMM间一共多少个数可以由111开始,通过 +x,+y,+z,归零(变成1)+x,+y,+z,归零(变成1)+x,+y,+z,归零(变成1) 四种操作得到(M<=50M<= 50M<=50)

我们可以爆搜!
数据范围50,爆搜可以解决所有问题!众生平等!

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<cmath>
#define debug(x) cout << x << "ok" << endl
typedef long long ll;
const int N = 50000007, M = 5000007, INF = 0x3f3f3f3f;
using namespace std;
int n, m;
ll h, ans;
bool vis[N];
int x, y, z;void dfs(ll now)
{if(now + x > h && now + y > h && now + z > h)return ;if(now + x <= h && vis[now + x] == 0){vis[now + x] = true;ans ++ ;dfs(now + x);}if(now + y <= h && vis[now + y] == 0){vis[now + y] = true;ans ++ ;dfs(now + y);}if(now + z <= h && vis[now + z] == 0){vis[now + z] = true;ans ++ ;dfs(now + z);}
}int main()
{//freopen("1.in", "r", stdin);//freopen("1.out", "w", stdout);scanf("%lld\n%d%d%d", &h, &x, &y, &z);ans = 1;dfs(1);cout << ans << endl;return 0;
}

那么如果我们加强一点呢?

给你一个数M,三个数x,y,z,问111 ~ MMM间一共多少个数可以由1开始,通过+x,+y,+z三种操作得到(M<=1000M<= 1000M<=1000)

我们发现这其实就是一个完全背包问题,因为每个操作可以无限使用,我们可以直接开一个数组f[1000],表示能凑到的方案数,最后直接判断一下所有1~M个完全背包。

如果我们在加强一点点呢?

给你一个数M,三个数x,y,z,问111 ~ MMM间一共多少个数可以由1开始,通过+x,+y,+z三种操作得到(M<=1000000000000000000M<= 1000000000000000000M<=1000000000000000000)

M的范围达到了101810^{18}1018,如此庞大的数据范围,我们不可能开的下101810^{18}1018的数组,也不可能循环101810^{18}1018次,所以我们必须考虑如何优化。

让我们来看一下由这个模型填充出来的一道同余最短路模板,让我们一步一步推出来同余最短路这一优化思路。

例题1:luogu P3403 跳楼机


1≤h≤263−1,1≤x,y,z≤105。1≤h≤2 ^{63} −1,1 \le x,y,z \le 10^5 。1≤h≤263−1,1≤x,y,z≤105。

这道题数据达到了101810^{18}1018,也就是第三种形态。

我们首先考虑如何统计答案,我们知道一个数 numnumnum 如果是由 y,zy,zy,z 构成的,即 num=a∗y+b∗znum=a∗y+b∗znum=a∗y+b∗z ,那么如果加上 xxx ,它能到达的楼层数为 (h−num)/x+1(h-num)/x+1(h−num)/x+1 其中 +1+1+1 是因为要统计不加 xxx 时的答案;

​ 那么如果我们能找到这些数,再去统计不就好了吗,但是会有重复,而且数字太过庞大,无法完全跑出,那么考虑什么时候回重复计数,即当 num1=num2+axnum1=num2+axnum1=num2+ax ,此时 num1num1num1 的贡献已经被 num2num2num2 统计过,再观察两个数字,发现 num1%x=num2%xnum1 \% x=num2 \% xnum1%x=num2%x,那么我们只要找到余数相同中最小值即可,余数共有 xxx 种,也就是x的剩余系。这样我们就可以把庞大的101810^{18}1018缩小到 xxx 的范围里(10510^5105)。

  • 我们使用同余的思想缩小了范围!

我们考虑题目中操作2和操作3(其实哪个都行,留下最小的那个最优) %x\%x%x 能到达的楼层,可以发现最终答案一定落在 xxx 的剩余系内。当我们知道这些合法的剩余系的时候,就可以不断累加x的值直到最大高度来求出可以到达的不同高度。

如何求出只用操作2,3在每个剩余系到达的最小高度?(因为更高的可以由最小高度递推出来,上面说了) 。我们设f(k)f(k)f(k)表示在xxx的剩余系内到达的楼层,根据DP的思想,如果此时使用操作2,3那么可以得出:

f((k+y)%x)=min{f(k)+y}f((k+y)\%x)=min\{f(k)+y\}f((k+y)%x)=min{f(k)+y}
f((k+z)%x)=min{f(k)+z}f((k+z)\%x)=min\{f(k)+z\}f((k+z)%x)=min{f(k)+z}

物理意义就是(k+y)%x(k+y)\%x(k+y)%x能到达的楼层实际上就是由k层使用操作2往上爬y层到达的,也就是从往上爬y层递推过来的(转移过来的),我们取最小值即可。

那么怎么求出由底层(k=0k=0k=0)到其他剩余系中元素的最小代价呢?我们发现状态转移方程f((k+y)%x)=min{f(k)+y}f((k+y)\%x)=min\{f(k)+y\}f((k+y)%x)=min{f(k)+y}和最短路的转移方程dist[y]=dist[x]+zdist[y] = dist[x] + zdist[y]=dist[x]+z非常相似,更重要的是,他们求的都是最小值!(参考差分约束,也是一个问题转移方程与最短路相似然后直接转化为最短路解决)

  • 我们利用最短路来优化了思路!

我们在最短路里的转移方程dist[y]=dist[x]+zdist[y] = dist[x] + zdist[y]=dist[x]+z是xxx向yyy链接一条权值为zzz的边,然后跑最短路。同样的,我们构建始点为 kkk,终点为k+y)%xk+y)\%xk+y)%x且长度为 yyy 的边,建完图以后,我们从 111 开始跑一遍最短路(因为我们在第一层)。对于操作3同理。(初始化f[1]=1f[1]=1f[1]=1,因为1层也算一个答案)

跑完最短路求出fff数组,表示的是通过使用操作2,3能够到达的最小楼层。然后使用最开始讲的公式,扫描一下x的剩余系,求一下答案即可。

解释一下为什么要循环x的整个剩余系,全部都连上边呢?因为我们这里实际上只是把整个框架搭好,因为我们不知道实际上会用到剩余系的哪一个点,所以我们把整个体系全部连上边,然后从起点开始跑最短路即可,最后到不了的点就不会用上。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<queue>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N = 200007, M = 500007, INF = 0x3f3f3f3f;ll h, x, y, z;
ll f[N], ans;
bool vis[N];
int head[N], ver[M], nex[M], edge[M], tot;void add(int x, int y, int z)
{ver[tot] = y;edge[tot] = z;nex[tot] = head[x];head[x] = tot ++ ;
}void spfa()
{memset(f, 0x3f, sizeof f);queue<int>q;q.push(1);vis[1] = true;f[1] = 1;while(q.size()){int x = q.front();q.pop();vis[x] = false;for(int i = head[x];~i;i = nex[i]){int y = ver[i];ll z = edge[i];if(f[y] > f[x] + z){f[y] = f[x] + z;if(!vis[y]){vis[y] = true;q.push(y);}}}}
}int main()
{memset(head, -1, sizeof head);scanf("%lld\n%lld%lld%lld", &h, &x, &y, &z);if(x == 1 || y == 1 || z == 1){cout << h << endl;return 0;}for(int i = 0; i < x; ++ i){add(i, (i + y) % x, y);//能够到达的楼层高度,+y是往上上y层add(i, (i + z) % x, z);}spfa();for(int i = 0; i < x; ++ i){if(f[i] <= h){ans += (h - f[i]) / x + 1;//+1是因为还要算f[i]这个楼层}}printf("%lld\n", ans);return 0;
}

例题2:luogu P2371 [国家集训队]墨墨的等式

这道题跟上一道题基本上一摸一样只不过有一点细微的差别

  • 这里不再是三个操作,而是改成了n种操作,思路是同样的,我们直接
  • 求的是一个区间的“楼层数”。
  • 从0开始,因为等式是从0开始的。
  • 初始化时dist[0]=0dist[0] = 0dist[0]=0
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define debug(x) cout << x << "ok" << endl
typedef long long ll;
#define file freopen("1.in", "r", stdin);freopen("1.out", "w", stdout);
const int N = 500007, M = 5000007, INF = 0x3f3f3f3f;
using namespace std;ll n, m, l, r;
ll dist[N];
ll f[N];
ll head[N], ver[M], nex[M], tot;
ll edge[M];
ll a[N];
bool vis[N];
queue<int>q;void init()
{memset(head, -1, sizeof head);tot = 0;
}void add(ll x, ll y, ll z)
{ver[tot] = y;edge[tot] = z;nex[tot] = head[x];head[x] = tot ++ ;
}void spfa(int s)//本题是从0开始
{memset(dist, 0x3f, sizeof dist);vis[s] = 1;dist[s] = 0;//这里存的是大小q.push(s);while(q.size()){int x = q.front();q.pop();vis[x] = 0;for(int i = head[x] ;~i; i = nex[i]){int y = ver[i];ll z = edge[i];if(dist[y] > dist[x] + z){dist[y] = dist[x] + z;if(!vis[y]){vis[y] = 1;q.push(y);}}}}
}ll query(ll m)
{ll res = 0;for(int i = 0; i < a[1]; ++ i){if(dist[i] <= m){res += (m - dist[i]) / a[1] + 1;}}return res;
}int main()
{init();scanf("%lld%lld%ld", &n, &l, &r);for(int i = 1; i <= n; ++ i){scanf("%lld", &a[i]);if(a[i] == 0)i -- , n -- ;}sort(a + 1, a + 1 + n);for(int i = 0; i < a[1]; ++ i){for(int j = 2; j <= n; ++ j){add(i, (i + a[j]) % a[1], a[j]);}}spfa(0);printf("%lld\n", query(r) - query(l - 1));return 0;
}

例题3:luogu P2662 牛场围栏

https://www.luogu.com.cn/problem/P2662

【算法笔记】一步一步推出来的同余最短路优化思路(千字长文,超详细)相关推荐

  1. 五千字长文为你揭秘滴滴共享出行派单算法原理(干货)

    关注ITValue,看企业级最新鲜.最具价值报道! 本文作者 | 滴滴首席算法工程师 导读:说到滴滴的派单算法,大家可能感觉到既神秘又好奇,从出租车扬召到司机在滴滴平台抢单最后到平台派单,大家今天的出 ...

  2. 7000字 Redis 超详细总结笔记总 | 收藏必备!

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | 王爷科技 来源 | www.toutiao.c ...

  3. list 查找_五千字长文带你学习 二分查找算法

    点击上方"与你一起学算法",选择"星标"公众号 重磅干货,第一时间送达 二分查找的思想 提及二分查找算法,我想大部分人都不陌生,就算不是学计算机的,基本上也都使 ...

  4. redis zset转set 反序列化失败_7000字 Redis 超详细总结、笔记!建议收藏

    Redis 简介 Redis 是完全开源免费的,遵守 BSD 协议,是一个高性能的 key - value 数据库 Redis 与 其他 key - value 缓存产品有以下三个特点: Redis ...

  5. java redis 原子操作_7000字 Redis 超详细总结笔记 !建议收藏

    Redis 简介 Redis 是完全开源免费的,遵守 BSD 协议,是一个高性能的 key - value 数据库 Redis 与 其他 key - value 缓存产品有以下三个特点: Redis ...

  6. Linux手动部署nlb步骤,一步一步配置NLB(续)之深入测试

    首先,准备一些工具: 1. Fiddler2,用来查看请求的分配情况,当然这个工具还有其他强大的功能,今天,我们只用很小的功能; 其次呢,我们了解一下NLB里面端口规则的设置: a.右键群集--> ...

  7. 算法笔记入门篇-多项式求和

    算法笔记入门篇-多项式求和 前言 1.题目描述 2.解题思路 3.代码实现 1.题目描述 给出两个多项式的系数,求出这两个多项式的乘积. 例如,第一个多项式为f(x)=2.4x+3.2,第二个多项式为 ...

  8. 一步一步写算法(之图结构)

    原文:一步一步写算法(之图结构) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 图是数据结构里面的重要一章.通过图,我们可以判断两个点之间是 ...

  9. 一步一步写算法(之prim算法 中)

    原文:一步一步写算法(之prim算法 中) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] C)编写最小生成树,涉及创建.挑选和添加过程 MI ...

最新文章

  1. 实时双频Wi-Fi如何实现下一代车内连接
  2. MPB:沈阳生态所李琪组-​土壤线虫群落DNA提取、扩增及高通量测序
  3. 机智云官网用到的库-grid.css我解析
  4. 年终盘点 | 七年零故障支撑 双11 的消息中间件 RocketMQ,怎么做到的?
  5. F4+2 团队项目软件设计方案
  6. netstat详解_需要!Linux常用监视和故障排查命令详解
  7. 函数实现不放在头文件的原因,及何时可以放头文件的情况【转】
  8. (37)FPGA面试题实现约翰逊计数器
  9. POJ 3071 概率DP
  10. 全世界都误会我们了。。。。。。。。
  11. 洛谷P1306 斐波那契公约数
  12. 计算机二级java判卷标准_计算机等级考试二级评分标准
  13. Simulink仿真---clark变换、反clark变换
  14. 怎么样可以对腾讯云IM-SDK集成(web端)完成IM登录
  15. 学会用CUPS管理打印机
  16. 青龙面板2.9,以及wskey自动转换ck配置
  17. 浊音/清音/爆破音的时域及频域特性
  18. android浏览器400错误代码,console.log在谷歌浏览器和Android浏览器的本地代码错误...
  19. 程序员初入职场第一年—— 程序员羊皮卷 连载 13
  20. 【全栈开发实战小草看书之Web端(八)主入口】

热门文章

  1. 如何保证工业相机工作的精准与稳定?
  2. 北大博士整理B站实战项目!yyds!
  3. 基于OpenCV的条形码检测
  4. 【OpenCV 4开发详解】图像二值化
  5. SpringBoot 学习 | raibaby halo 之安装部署 - Ali0th
  6. LongAdder解析
  7. 小目标 | DAX高级实践-Power BI与Excel联合应用
  8. 在MAPGIS中,怎么投影多条线段?
  9. Saltstack笔记
  10. 日常积累的一些linux和运维的东西 [转]