「HNOI2016」大数
题目描述
给定一个质数\(p\)和一个数字序列,每次询问一段区间\([l,r]\),
求出该序列区间\([l,r]\)内的所有子串,满足该子串所形成的数是\(p\)的倍数(样例的解释也挺直观的)
基本思路
这题的话,满足莫队的离线查询套路,所以用莫队蛮好写。
我们考虑这样一个思路:
众所周知,判断两个数的倍数关系是通过取模来实现的,所以我们考虑对于每一个位置$\ i\ \(,定义一个\)s_i$
表示\(\overline{a_ia_{i+1}...a_n}\)这个后缀模$\ p\ \(的值,再把它离散化,这样我们就可以实现单次移动在\)O(1)时间内完成$(见下)
inline void upt(int x, int v) {
//x为传入的s的值,v是当前操作带来的变化值(1则为加上贡献,-1则为减去贡献)
//tong是计数器,存储当前s的数量
ans -= tong[x] * (tong[x] - 1) / 2;
tong[x] += v;
ans += tong[x] * (tong[x] - 1) / 2;
}
搞定这一步,接下来考虑\(s\)如何转移:
由于是求后缀,我们考虑从后往前枚举\(a_i\),我们推一下式子
\]
所以我们只需要在枚举的时候不断更新\(10^{n-i+1}\)即可
然后我们就可以发现,如果一个区间\([l,r]\)所表示的数\(\overline{a_la_{l+1}...a_r}\),它如果是\(p\)的倍数的话,显然有\(s_l=s_{r+1}\)
小小的证明:
\]
\]
\]
\]
但是!!!
在上述的证明中第一次推导是不严谨的,因为可能存在\(p\mid10\)使得原式成立,这就引来了重点的分类讨论
对于\(p\nmid10\)也就是\(p\ne2\)且\(p\ne5\)时,我们可以用上述方法,结合莫队来离线求解。
而对于\(p=2\)或\(p=5\)的情况,我们直接写特判:
我们发现,判断一个数是不是\(2\)或\(5\)的倍数,只需要看个位数字是否被该数字整除即可。
所以我们考虑对于每一个位置\(i\),记录下区间\([1,i]\)中的\(p\)的个数及总贡献:
if (p == 2) bo[0] = bo[2] = bo[4] = bo[6] = bo[8] = 1;
if (p == 5) bo[0] = bo[5] = 1;
for (rg int i = 1; i <= n; ++i) {
f[i] = f[i - 1] + bo[num[i] ^ 48];//f为区间[1,i]中的p的个数
g[i] = g[i - 1] + bo[num[i] ^ 48] * i;
//g为区间[1,i]的总贡献,乘上一个i是因为乘法原理,这个可以自己简单想一想
}
查询贡献时我们就可以直接在线搞:
m = read();
for (rg int l, r, i = 1; i <= m; ++i) {
l = read(), r = read();
/*-----想一想为什么-----*/
printf("%lld\n", g[r] - g[l - 1] - (f[r] - f[l - 1]) * (l - 1));
}
那么至此这道题就解决了。
细节注意事项
- 这题应该要开\(long\ long\)
- 莫队别写挂了。。。
参考代码
/*--------------------------------
Code name: HNOI2016 BigNumber
Author: The Ace Bee
This code is made by The Ace Bee
--------------------------------*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define rg register
#define int long long
const int MAXN = 200010;
inline int read() {
int s = 0; bool f = false; char c = getchar();
while (c < '0' || c > '9') f |= (c == '-'), c = getchar();
while (c >= '0' && c <= '9') s = (s << 3) + (s << 1) + (c ^ 48), c = getchar();
return f ? -s : s;
}
char num[MAXN]; int bo[10], f[MAXN], g[MAXN];
int p, m, s[MAXN], s1[MAXN], gap, pos[MAXN];
struct Ask{ int l, r, id; }q[MAXN];
inline bool cmp(const Ask& x, const Ask& y)
{ return pos[x.l] == pos[y.l] ? x.r < y.r : x.l < y.l; }
int ans, res[MAXN], cnt[MAXN];
inline void upt(int x, int v) {
ans -= cnt[x] * (cnt[x] - 1) / 2;
cnt[x] += v;
ans += cnt[x] * (cnt[x] - 1) / 2;
}
signed main() {
p = read();
scanf("%s", num + 1);
int n = strlen(num + 1);
if (p != 2 && p != 5) {
m = read();
for (rg int i = 1; i <= m; ++i) q[i].l = read(), q[i].r = read() + 1, q[i].id = i;
gap = sqrt(n * 1.0);
pos[n + 1] = n / gap + 1;
for (rg int c = 1, i = n; i >= 1; --i, c = c * 10 % p) {
s1[i] = s[i] = ((num[i] ^ 48) * c % p + s[i + 1]) % p;
pos[i] = (i - 1) / gap + 1;
}
std :: sort(s1 + 1, s1 + 2 + n);
int t = std :: unique(s1 + 1, s1 + 2 + n) - s1 - 1;
for (rg int i = 1; i <= n + 1; ++i)
s[i] = std :: lower_bound(s1 + 1, s1 + 1 + t, s[i]) - s1;
std :: sort(q + 1, q + 1 + m, cmp);
for (rg int l = 1, r = 0, i = 1; i <= m; ++i) {
while (l < q[i].l) upt(s[l++], -1);
while (l > q[i].l) upt(s[--l], 1);
while (r < q[i].r) upt(s[++r], 1);
while (r > q[i].r) upt(s[r--], -1);
res[q[i].id] = ans;
}
for (rg int i = 1; i <= m; ++i) printf("%lld\n", res[i]);
} else {
if (p == 2) bo[0] = bo[2] = bo[4] = bo[6] = bo[8] = 1;
if (p == 5) bo[0] = bo[5] = 1;
for (rg int i = 1; i <= n; ++i) {
f[i] = f[i - 1] + bo[num[i] ^ 48];
g[i] = g[i - 1] + bo[num[i] ^ 48] * i;
}
m = read();
for (rg int l, r, i = 1; i <= m; ++i) {
l = read(), r = read();
printf("%lld\n", g[r] - g[l - 1] - (f[r] - f[l - 1]) * (l - 1));
}
}
return 0;
}
完结撒花\(qwq\)
「HNOI2016」大数的更多相关文章
- loj2053 「HNOI2016」大数
ref #include <algorithm> #include <iostream> #include <cstring> #include <cstdi ...
- 「HNOI2016」数据结构大毒瘤
真是 \(6\) 道数据结构毒瘤... 开始口胡各种做法... 「HNOI2016」网络 整体二分+树状数组. 开始想了一个大常数 \(O(n\log^2 n)\) 做法,然后就被卡掉了... 发现直 ...
- 「HNOI2016」树 解题报告
「HNOI2016」树 事毒瘤题... 我一开始以为每次把大树的子树再接给大树,然后死活不知道咋做,心想怕不是个神仙题哦 然后看题解后才发现是把模板树的子树给大树,虽然思维上难度没啥了,但是还是很难写 ...
- 「HNOI2016」序列 解题报告
「HNOI2016」序列 有一些高妙的做法,懒得看 考虑莫队,考虑莫队咋移动区间 然后你在区间内部找一个最小值的位置,假设现在从右边加 最小值左边区间显然可以\(O(1)\),最小值右边的区间是断掉的 ...
- 「HNOI2016」网络 解题报告
「HNOI2016」网络 我有一个绝妙的可持久化树套树思路,可惜的是,它的空间是\(n\log^2 n\)的... 注意到对一个询问,我们可以二分答案 然后统计经过这个点大于当前答案的路径条数,如果这 ...
- 「HNOI2016」最小公倍数 解题报告
「HNOI2016」最小公倍数 考虑暴力,对每个询问,处理出\(\le a,\le b\)的与询问点在一起的联通块,然后判断是否是一个联通块,且联通块\(a,b\)最大值是否满足要求. 然后很显然需要 ...
- loj #2051. 「HNOI2016」序列
#2051. 「HNOI2016」序列 题目描述 给定长度为 n nn 的序列:a1,a2,⋯,an a_1, a_2, \cdots , a_na1,a2,⋯,an,记为 a[1: ...
- 【LOJ】#2052. 「HNOI2016」矿区
题解 之前尝试HNOI2016的时候弃坑的一道,然后给补回来 (为啥我一些计算几何就写得好长,不过我写啥都长orz) 我们尝试给这个平面图分域,好把这个平面图转成对偶图 怎么分呢,我今天也是第一次会 ...
- loj#2049. 「HNOI2016」网络(set 树剖 暴力)
题意 题目链接 Sol 下面的代码是\(O(nlog^3n)\)的暴力. 因为从一个点向上只会跳\(logn\)次,所以可以暴力的把未经过的处理出来然后每个点开个multiset维护最大值 #incl ...
随机推荐
- eclipse 自定义代码块设置(代码模板)
eclipse设置自定义代码模板 window -> show View -> other -> Templates 原来main模板 修改模板 再次插入
- HDU4081 Qin Shi Huang's National Road System
先求最小生成树 再遍历每一对顶点,如果该顶点之间的边属于最小生成树,则剪掉这对顶点在最小生成树里的最长路径 否则直接剪掉连接这对顶点的边~ 用prim算法求最小生成树最长路径的模板~ #include ...
- 201771010135 杨蓉庆/张燕/杨玲《面对对象程序设计(java)》第十四周学习总结
1.实验目的与要求 (1) 掌握GUI布局管理器用法: (2) 掌握各类Java Swing组件用途及常用API 一.理论知识 Swing和MVC设计模式 (1)设计模式(Design patte ...
- java篇 之 多态
2018-9-28 多态: 重载也称为静态多态(静态在编译阶段就能确定)(动态是跟运行时挂钩) 尽量去选择关系轻的,降低耦合度(紧密度) 内聚: 减少与外界的联系,降低与其他对象和类的联系 对象与对象 ...
- ajax请求ashx跨域问题解决办法
ajax请求ashx跨域问题解决办法 https://blog.csdn.net/windowsliusheng/article/details/51583566 翻译windowsliusheng ...
- jq 获取input多选框
1.获取checkbox选中个数 $("input[name='ckb-jobid']:checked").length $("input[type='checkbox' ...
- Java日期时间API系列8-----Jdk8中java.time包中的新的日期时间API类的LocalDate源码分析
目录 0.前言 1.TemporalAccessor源码 2.Temporal源码 3.TemporalAdjuster源码 4.ChronoLocalDate源码 5.LocalDate源码 6.总 ...
- leetcode菜鸡斗智斗勇系列(10)--- Decrypt String from Alphabet to Integer Mapping
1.原题: https://leetcode.com/problems/decrypt-string-from-alphabet-to-integer-mapping/submissions/ Giv ...
- sklearn笔记:决策树
概述 sklearn中决策树的类都在 tree 这个模块下.这个模块总共包含五个类: tree.DecisionTreeClassifier:分类树 tree.DecisionTreeRegresso ...
- 201771010135杨蓉庆《面向对象程序设计(java)》第六周学习总结
实验六 继承定义与使用 1.实验目的与要求 (1) 理解继承的定义: (2) 掌握子类的定义要求 (3) 掌握多态性的概念及用法: (4) 掌握抽象类的定义及用途: (5) 掌握类中4个成员访问权限修 ...