Logtrick
logtrick的用法与实战
logtrick是我从灵神视频中学习到的,此文章介绍logtrick用法与实践,以及灵神视频中未提到的,我本人总结出来的小技巧
用法
logtrick通常用于求 子数组(gcd,lcm,&,|)后的max或者min或者计数问题
子数组问题
logtrick主要是解决子数组问题,所以在此我们先引出子数组问题
什么是子数组问题?
就是求 f(l,r)的所有操作的(最优值 或者 计数值) 1<=l<=r<=n
类似这个公式:
但不一定是求和,应是题目要求的求(最优值 或者 计数值)等
**这个f即函数 是求区间[l,r]完成题目特定操作后 得到的特定值 **
比如说:f(l,r),f函数是求min的 那么就是求[l,r]区间中最小的一个值
如何高效计算子数组问题
计算子数组,有一种可行思路是把注意力放在右端点,通过不断移动右端点,在移动的时候利用前面的统计结果算出移动右端点后的结果,从而得出所有子区间的答案。
这种方法经典套路有 前缀和,logtrick
今天我们重点是logtrick
logtrick是基于O(n^2)的计算子数组问题 利用(|,&,lcm,gcd)等性质 优化的一种套路亦或者是算法
先介绍o(n^2)的算法
for(int i = 0; i < n;i++){
for(int j = i - 1;j >= 0;j--){
a[j] ?= a[i]; //此问号可以为gcd lcm | &
}
}
这个算法挺清晰的我们研究一下过程,然后考虑优化
比如说 求a的子数组按位&等于k的子数组的总和
给出数据与对应二进制:
7 3 4 1
111 011 100 001
操作流程:
可以发现这样枚举右端点更新左端点可以保证求出所有的子数组的运算结果
优化:
这是最最最重点的了我们发现第三次到第四次过程中是没有变化的,为什么呢,因为&越多数&的的总结局是越来越小
而&不变就是说明了我这个数已经不足以让当前位置变小,又因为当前位置的前面都是与当前位置&过的,所以不能让当前位置变小也自然不会让前面位置再变小,这点很难理解用灵神的话来讲就是看做是集和的关系,我后面枚举的这个数设为x & 当前这个数设为y然后 y不变小, 意味这 y 一定是x的子集 而y前面的数都&过y 说明都是与y的交集了,与x交就相当于没有交; 很难理解借助图形理解
图形
我们设当前枚举的是x 然后 x&y=y , 然后y前面的数为z z&y设为 t
前面的数组的记录的数t是这样的
y是x的子集所以加入x长这样
虚线部分是x,可以看到加入x是不会对y和y前面的t产生影响的 因为 x与y与z的交集还是t
所以当我们遇到 a[j] & a[i] == a[j] 时 直接break ,不必管前面的数
就因为这个小优化我们的时间复杂度可以从
O(n^2) 变为 O(nlogU)
u为数组中最大值
因为我们一个数不断缩小就加强了成为x的子集的可能性,就更可能的触及 a[j] & a[i] == a[j] 这个条件,而最坏也是log级别的缩小,此为logtrick精髓所在
除了& 还有| gcd lcm 殊途同归读者自己思考
模版
for(int i = 1; i <= n;i++){
int x = nums[i];
//这里可以进行一些操作
for(int j = i - 1;j >= 1;j--){
if((nums[j] 操作 nums[i]) = ?){
break;
}
nums[j] 操作= nums[i];
}
然后进行二分或者一些操作
}
小技巧
logtrick 配合 map使用有出其不意的效果,甚至说更加无脑与方便,下面题目有讲
实战
简单题
源于灵神总结的题单
力扣3097
3097. 或值至少为 K 的最短子数组 II - 力扣(LeetCode)
思路: 因为如果 nums[j] | nums[i] == nums[i] 就代表前面都不会再变大,所以直接结束,很经典的板子题
class Solution {
public:
int minimumSubarrayLength(vector<int>& nums, int k) {
int mi = 0x3f3f3f3f;
int n = nums.size();
for(int i = 0; i < n;i++){
if(nums[i] >= k){
return 1;
}
for(int j= i - 1; j >= 0 && (nums[j] | nums[i]) != nums[j];j--){
nums[j] |= nums[i];
if(nums[j]>=k){
mi = min(mi,i-j+1);
}
}
}
return mi==0x3f3f3f3f ? -1 : mi;
}
};
力扣2411
2411. 按位或最大的最小子数组长度 - 力扣(LeetCode)
思路:和上题类似
class Solution {
public:
vector<int> smallestSubarrays(vector<int>& nums) {
int n = nums.size();
vector<int> ans(n,1);
for(int i = 0; i < n;i++){
int x= nums[i];
for(int j = i - 1 ;j >= 0 && (nums[j] | nums[i]) != nums[j];j--) {
nums[j]|=nums[i],ans[j] = i - j + 1;
}
}
return ans;
}
};
力扣3209
3209. 子数组按位与值为 K 的数目 - 力扣(LeetCode)
思路: 该题更快做法应该是二分,但是这里我们用无脑的map计数直接无视二分,让问题难度直线下降
class Solution {
public:
long long countSubarrays(vector<int>& nums, int k) {
long long ans = 0;
int n = nums.size();
map<int,int> h;
for(int i = 0;i<n;i++){
int x = nums[i];
h[x]++;
for(int j = i - 1; j >= 0 && (nums[j] & nums[i]) != nums[j];j--){
h[nums[j]]--;
nums[j]&=nums[i];
h[nums[j]]++;
}
ans+=h[k];
}
return ans;
}
};
力扣1521
1521. 找到最接近目标值的函数值 - 力扣(LeetCode)
思路: 简单板子运用
class Solution {
public:
int closestToTarget(vector<int>& nums, int target) {
int ans = 0x3f3f3f3f;
for(int i = 0;i < nums.size();i++){
ans = min(ans,abs(nums[i] - target));
for(int j = i - 1;j >= 0;j--){
if((nums[j]&nums[i]) == nums[j]) break;
nums[j]&=nums[i];
ans = min(ans,abs(nums[j] - target));
}
}
return ans;
}
};
力扣898
思路:计数问题带个map 会好解决很多
class Solution {
public:
int subarrayBitwiseORs(vector<int>& a) {
int n = a.size();
map<int,int> h;
for(int i = 0;i<n;i++){
h[a[i]]++;
for(int j = i - 1; j >= 0;j--){
if((a[j]|a[i])==a[j]) break;
h[a[j]|a[i]]++;
a[j]|=a[i];
}
}
return h.size();
}
};
中等题
CF475D
思路: 运用map然后累加到res中
const int N = 200005;
int n,m;
map<int,int> res;
int a[N];
void solve(){
cin >> n;
for(int i = 1; i <= n;i++){
cin >> a[i];
}
map<int,int> h;
for(int i = 1; i <= n;i++){
h[a[i]]++;
for(int j = i - 1; j >=1;j--){
if(__gcd(a[i],a[j]) == a[j]){
break;
}else{
h[a[j]]--;
if(h[a[j]]==0) h.erase(a[j]);
a[j] = __gcd(a[i],a[j]);
h[a[j]]++;
}
}
for(auto p:h) res[p.first]+=p.second;
}
int q;
cin >> q;
while(q--){
int t;
cin >> t;
cout << res[t] <<endl;
}
}
困难题
CF1632D
此题是我做到目前为止 logtrick中最难的题,但是其实也不是很难啦,就是相对别的题目比较难,就是要灵活掌握变形
思路:思维+logtrick,首先我们要清楚要贪心修改数字必定是把数字修改成1,因为这样才不会影响后面的数,然后就是如果修改了前面的子数组的一个元素,前面的子数组就全部没用了,直接map.claer(),然后做logtrick时候j那一维度为不用遍历到前面了
const int N = 200005;
int n,m;
map<int,int> res;
int a[N];
void solve(){
cin >> n;
for(int i = 1; i <= n;i++){
cin >> a[i];
}
map<int,set<int> > h;
int cnt = 0;
int lt = 1;
for(int i = 1; i <= n;i++){
h[a[i]].insert(i);
for(int j = i - 1;j>=lt;j--){
if(__gcd(a[j],a[i])==a[j]){
break;
}
h[a[j]].erase(j);
if(h[a[j]].size()==0) h.erase(a[j]);
h[__gcd(a[j],a[i])].insert(j);
a[j]=__gcd(a[j],a[i]);
}
//i-j+1 == k
bool f = false;
for(auto &[k,se]:h){
if(se.count(i - k + 1)){
cnt++;
f = true;
break;
}
}
if(f){
map<int,set<int> > h2;
h=h2;
h[1].insert(i);
lt=i+1;
}
cout << cnt << " ";
}
}
最后感谢@灵茶山艾府
基础起源于灵神的教学,题单也是灵神那里找的
Logtrick的更多相关文章
- Flink + 强化学习 搭建实时推荐系统
如今的推荐系统,对于实时性的要求越来越高,实时推荐的流程大致可以概括为这样: 推荐系统对于用户的请求产生推荐,用户对推荐结果作出反馈 (购买/点击/离开等等),推荐系统再根据用户反馈作出新的推荐.这个 ...
随机推荐
- 嵌入式编程中使用qemu能够做什么?
嵌入式编程中使用qemu能够做什么? 背景 学习QEMU以后,为了拓展视野,才发现QEMU可以做这么多的事情. 原文链接(有删改):https://www.eet-china.com/mp/a5634 ...
- Zynq-7045升级,ARM+FPGA性能怪兽,Xilinx UltraScale+ MPSoC XCZU7EV核心板正式发布
- SpringBoot 整合 Sharding-JDBC 分库分表
导读 分库分表的技术有:数据库中间件Mycat(点我直达),当当网开源的Sharding-JDBC:我们公司用的也是sharding-jdbc,自己也搭建一个完整的项目,直接可以拿来用.下面附源码(C ...
- 2024-07-13:用go语言,给定一个从0开始的长度为n的整数数组nums和一个从0开始的长度为m的整数数组pattern,其中pattern数组仅包含整数-1、0和1。 一个子数组nums[i.
2024-07-13:用go语言,给定一个从0开始的长度为n的整数数组nums和一个从0开始的长度为m的整数数组pattern,其中pattern数组仅包含整数-1.0和1. 一个子数组nums[i. ...
- webgl(threejs)生成房间楼层
楔子 在很多数字孪生项目中,都会涉及到楼层的建模.楼层的建模由于结构繁多,如果都是建模师进行手动建模,工作量会比较大.而楼层本身的结构,可以抽象成可以通过路径构造的对象(这和之前的文章提及的的管路以及 ...
- 拯救SQL Server数据库事务日志文件损坏的终极大招
拯救SQL Server数据库事务日志文件损坏的终极大招 在数据库的日常管理中,我们不可避免的会遇到服务器突然断电(没有进行电源冗余),服务器故障或者 SQL Server 服务突然停掉, 头大的是l ...
- oeasy教您玩转vim - 29 - # 垂直翻页
垂直翻页 回忆上节课内容 我们上次了解了横向滚动的相关信息 横滚幅度 - sidescroll 横滚留位 - sidescrolloff 自动换行 - wrap g j.g k 可以逐行上下移动 ...
- JuiceFS 直连 NFS 新功能介绍,赋能 NAS 进行 AI 训练
NAS 通过提供多用户网络数据存取服务,极大地简化了数据共享和管理.而 NFS 作为实现这种共享的一种主流协议,尽管广泛应用,但在处理复杂的 AI 训练场景时常常受限于其性能和一致性问题. Juice ...
- php 模型浏览器
docker安装 文档:https://github.com/SeleniumHQ/docker-selenium docker run -d -p 4444:4444 -p 7900:7900 -- ...
- linux的一些常用端口
hdfs:9870 yarn:8088 sparkMaster的端口是:8080 worker的端口是:8081 历史服务器的默认端口是: 18080