首页 > 其他分享 >『做题记录』厉害trick集

『做题记录』厉害trick集

时间:2024-09-12 15:51:50浏览次数:12  
标签:阈值 记录 int 厉害 pri 警报器 trick ans id

  不出意外的话,这就是我最后的波纹了吧。

  当然以后还会继续的。

减半警报器

  这个 trick 能将 \(n^2\) 的东西硬生生优化到 \(n\log^2\) ,还是很厉害的 trick

P7603 [THUPC2021] 鬼街

Description

  鬼街上经常有灵异事件,每个灵异事件会导致编号为 \(x\) 的质因子的房子闹 \(y\) 次鬼,鬼街上也会装警报器,一个警报器会在其安装后统计编号为 \(x\) 的质因子的房子闹鬼次数总和,这个值达到 \(y\) 时就会报警。特别的,当 \(y = 0\) 时,下一个灵异事件发生时这个警报器就会报警,无论灵异事件的参数如何。要求每个灵异事件会触发的警报器数量以及被触发警报器的编号。
  每次输入的 \(y=y'\oplus lastans\) , \(lastans\) 初始为 \(0\) ,在每次灵异事件后变为本次灵异事件会触发的警报器数量,即询问在线。
   \(n,q\leq 10^5,0\leq y\leq 2^{32}\)

Solution

  考虑到每次的质因数个数函数 \(\omega(n)\) 在数据范围内不会超过 \(6\) ,所以考虑在装警报器时直接在这不超过 \(6\) 个位置丢一个小警报器,每次灵异事件时就查一遍所有警报器所属的小警报器计数之和是否达到报警阈值。这样的做法是 \(O(\omega(n)n^2)\) 的,显然不足以通过。

  考虑优化上面的过程,我们发现警报器进行 “拿出来查询,然后再放回去无事发生” 这个过程的次数会非常多,这启发我们要减少这个过程的次数,而优化用到了一个性质:根据鸽巢原理,如果触发警报,那么所有小警报器计数的最大值一定大于 \(\left\lceil\dfrac y{\omega(x)}\right\rceil\) 。所以我们不需要每一次都处理每一个存在的警报器,而是 设置一个分阈值 ,每一次询问只处理那些到达这一“分阈值”的小警报器的小警报器。但是做到这一步也只相当于在复杂度上乘一个常数,并没有优化,甚至因为“分阈值”这一额外信息的维护而多了一只 \(\log\) 。

  还是继续考虑 “拿出来查询,然后再放回去无事发生” 这一过程,我们刚刚对 “拿出来” 的条件进行了优化,而 “放回去” 还没有被我们优化。很自然地,我们会考虑放回去的时候通过 更新分阈值 的方法减少次数:即把旧的阈值减去这一趟所有小警报器的计数和,然后将这一新阈值用同样的方法设置分阈值丢回去。因为每一次最坏的情况是只有达到分阈值的那个小警报器计数为分阈值,其他小警报器计数为 \(0\) ,这样相当于总阈值只减小为原来的 \(\dfrac {\omega(x)-1}{\omega(x)}\) 。

  这样一来我们执行 “拿出来查询,然后再放回去无事发生” 这一过程的次数只会有以 \(\dfrac {\omega(x)-1}{\omega(x)}\) 为底的对数次,也就是说我们成功的将复杂度从 \(O(n^2)\) 降到了 \(O(n\log n\log V)\) , \(\log n\) 来自维护分阈值所需的数据结构, \(\log V\) 则是我们刚刚分析的关于值域的底数。

  这个 trick 的理论分析就是这些,本质在于 设置阈值和更新阈值 所带来的复杂度优化。具体如何维护这一过程可以看代码。

Code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define mp make_pair
#define vi vector<int>
#define eb emplace_back
#define pli pair<LL, int>
#define fi first
#define se second
#define rep(rp,a,b) for(int rp=a;rp<=b;++rp)
#define per(bl,a,b) for(int bl=a;bl>=b;--bl)
#define segc int mid = L+R>>1, lc = now<<1, rc = lc|1
const int N = 1e5+5, MOD = 998244353, INF = 1e9;
inline LL read() {
	LL x = 0, f = 1; char ch = 0;
	while (!isdigit(ch)) {ch = getchar(); if (ch == '-') f = -1;}
	while (isdigit(ch)) x = (x<<3)+(x<<1)+(ch^48), ch = getchar();
	return f*x;
}

struct BLCK{
	int id, opid; LL lim;//监视器编号、阈值编号与阈值
	bool operator<(const BLCK &x) const {
		return lim > x.lim;
	}
};

int n, m, vis[N], cntn, nop[N];//nop i标记监视器i最新阈值编号
LL cnt[N];//从开始到现在闹鬼的次数
vi pri[N], ans;
priority_queue<BLCK> q[N];
pair<int, LL> mon[N];//每个监视器的监视目标和阈值

void sieve() {
	rep (i, 2, n) {
		if (!pri[i].empty()) continue ;
		rep (j, 1, n/i) pri[i*j].eb(i);
	}
}

LL getval(int x) {
	LL ret = 0;
	for (int j:pri[mon[x].fi]) ret += cnt[j];
	return ret;
}

int main() {
	n = read(), m = read();
	sieve();
	LL lans = 0;
	while (m --) {
		int opt = read(), x = read();
		LL y = read()^lans;
		if (!opt) {
			for (int i:pri[x]) cnt[i] += y;
			for (int i:pri[x]) {
				while (!q[i].empty() && q[i].top().lim <= cnt[i]) {
					BLCK x = q[i].top(); q[i].pop();
					if (vis[x.id]) continue ;
					if (nop[x.id] != x.opid) continue ;//类似dij的懒删除堆,忽略编号不为最新的阈值
					LL newval = getval(x.id);
					if (newval >= mon[x.id].se) vis[x.id] = 1, ans.eb(x.id);
					else {
						LL newlim = (mon[x.id].se-newval-1)/pri[mon[x.id].fi].size()+1;
						++nop[x.id];
						for (int j:pri[mon[x.id].fi])
							q[j].push((BLCK){x.id, nop[x.id], newlim+cnt[j]});
					}
				}
			}
			sort(ans.begin(), ans.end());
			printf("%d", (int)ans.size());
			for (int i:ans) printf(" %d", i); puts("");
			lans = ans.size();
			ans.clear();
		} else {
			++cntn;
			if (!y) {
				ans.eb(cntn);
				continue ;
			}
			LL lim = (y-1)/pri[x].size()+1, stp = 0;
			++nop[cntn];
			for (int i:pri[x]) {
				q[i].push((BLCK){cntn, nop[cntn],lim+cnt[i]});
				stp += cnt[i];
			} mon[cntn] = mp(x, y+stp);
		}
	}
	return 0;
}

Summary

  很经典的 trick (according to Cust10),但是优化幅度不算大,所以对应的数据范围也是比较好看出来的。

待更新……

标签:阈值,记录,int,厉害,pri,警报器,trick,ans,id
From: https://www.cnblogs.com/BlackCrow/p/18410311

相关文章

  • 解决vue3 useRoute无法获取get参数记录
    总结:使用route.query无法获取到get参数,开发模式代码改动能拿到,但是刷新又没了,需要监听route.query才能拿到get参数。正文:我的常规使用方法:先安装vue-routernpminstallvue-router@next创建src/router/index.js:import{createRouter,createWebHistory}from'vue-rou......
  • LeetCode Hot100刷题记录-142. 环形链表 II
    给定一个链表的头节点head,返回链表开始入环的第一个节点。如果链表无环,则返回null。如果链表中有某个节点,可以通过连续跟踪next指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数pos来表示链表尾连接到链表中的位置(索引从0开始)。如果pos是......
  • 记录一下,把tree中的部分数据剔除
      <scriptsetuplang="ts">import{js_beautifyasbeautify}from"js-beautify";importhljsfrom"highlight.js";import"highlight.js/styles/atom-one-dark.css";consttree=ref([{name:"第一层&......
  • 想想都可怕,恢复微信聊天记录原来这么简单
    互联网时代,尤其是微信聊天记录对部分人来说至关重要,在平时在使用微信时,会随手删掉暂时不用的好友的对话框,等回过神才发现,聊天记录也被顺手清空了,而有些好友的聊天记录很重要,可能是合同细节,或是有转账凭证等,要是一不小心删除了重要的聊天记录怎么找回呢?没想到恢复方法竟然这么简......
  • logging模块用于记录日志的标准库
    日志级别是监控和调试软件系统的关键组成部分,它们帮助开发者和运维人员区分不同严重程度的信息,从而更有效地响应和解决问题。以下是日志级别的详细说明及如何在Python中使用它们的示例。日志级别分类日志级别按严重程度从低到高排序如下:DEBUG:用于记录详细的调试信息,通常在开......
  • Arch Linux 安装记录
    ArchLinux个人直接在arch中使用arch-install-scripts安装新系统,一些前面的步骤没有记录。其中的步骤可以用GUI软件逃课。分区和格式化可以使用partitionmanager(Linux)、diskgenius(Windows)等GUI软件一键分区。partitionmanager在安装btrfs-progs后可以格式化分区为......
  • 《人工智能教育技术学》收获记录1
    2024年9月10日,人工智能教育技术学第二节课,感谢王老师耐心指导我们注册博客园,才有以下我发布的这条内容。本节课,我主要收获了关于我们手中搜索引擎如何使用的知识。搜索引擎是指根据一定的策略、运用特定的计算机程序从互联网上采集信息,在对信息进行组织和处理后,为用户提供检索服......
  • 【C#生态园】高效管理日志:C# 开发者不可错过的六大日志记录库
    C#日志记录库大比拼:选择最适合你的工具前言在C#应用程序开发过程中,日志记录是一个至关重要的方面。它不仅可以帮助开发人员跟踪应用程序的运行状态和故障信息,还能为用户提供更好的支持和维护服务。本文将介绍几个流行的C#日志记录库,包括Serilog、NLog、Log4net、Elm......
  • 打靶记录17——pyexpvm
    注意:使用VMWare打开靶机,不要使用VirtualBox打开,因为作者设计的就是使用VMware的靶机(中等难度):https://download.vulnhub.com/pyexp/pyexpvm.zip目标:取得root权限+2Flag攻击方法:主机发现端口扫描信息收集SSH密码爆破Mysql密码爆破Mysql执行代码编写......
  • LeetCode Hot100刷题记录-141.环形链表
    给你一个链表的头节点head,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪next指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数pos来表示链表尾连接到链表中的位置(索引从0开始)。注意:pos不作为参数进行传递。仅仅是为了标识链表的......