CCF计算机软件能力认证试题练习:201912-5 魔数

前置知识:BFS,线段树等

\(f(x) = (x\%A)\%B\)

这个函数值的和直接用线段树维护是不太行的(也可能是我不知道),后来想了很久的取模技巧操作...但是越想越远根本不着边际

网上也找不到题解,就去大佬群里面求助,cls一两句话就解决了这道题...

这几个U随便乘,最后只会有不到40个本质不同的数字

关于这个结论是如何得到的,不清楚,但是是很容易验证的,代码如下:

由于数字刚好不超过无符号longlong的范围,所以可以用快速乘来解决(复杂度log),算法类似于快速幂,如果没有了解过的同学可以去学一下

ull U[5] = {
314882150829468584,
427197303358170108,
1022292690726729920,
1698479428772363217,
2006101093849356424
};
ull mod = 2009731336725594113;
unordered_map<ull, int> mp;
ull f[35],a[N][35];
int g[35][35];
ull mul(ull a, ull b){
ull res = 0;
for(;b;b>>=1){
if(b & 1) res = (res + a) % mod;
a = (a + a) % mod;
}
return res;
}
void getConvert(){
queue<ull> q;
int id = 1;
for(int i=0;i<5;i++)q.push(U[i]), mp[U[i]] = id++;
//BFS找到所有可能的结果
while(q.size()){
ull x = q.front();q.pop();
f[mp[x]] = x;
for(int i=0;i<5;i++){
ull t = mul(x, U[i]);
if(mp[t]) {
continue;
}
mp[t] = id++;
q.push(t);
}
}
// 得到转移函数
for(int i=1;i<=32;i++){
for(int j=i;j<=32;j++){
g[i][j] = g[j][i] = mp[mul(f[i], f[j])];
}
}
}

然后该怎么解决?线段树维护区间乘这32种数字的结果和。

因为不管怎么乘,最终结果只会对应乘的是这32个数字中的某一个。

对于线段树上的某个节点,\(s[i]\) 表示区间内每个数乘了第 i 个数字即 \(f[i]\) 后模2019的和。

对于每次修改,假如要乘第 \(f[i]\) 个数字,那么对于 \(j \in [1,32], s[j] = s[g[j][i]]\) g数组的含义见上述代码,也就是 本来这个区间都乘了 \(f[j]\),然后又乘了个 \(f[i]\),那么最终就会发生转移,相当于乘第 \(g[j][i]\) 个数字,对应于原来的 \(s[g[j][i]]\)

然后区间要打标记,如果之前没有标记过,那么 \(tag = i\) 表示当前乘了第 i 个数字,否则\(tag = g[tag][i]\)进行转移

线段树上面的具体操作就是这样,但是我兴高采烈的写完就交了上去,一直T到怀疑人生。

初始化线段树节点只有 2n 个,每个初始化32次,复杂度根本不会炸

每次查询是 \(32\log n\) ,总共1e5次查询,也不会炸,那是为什么呢?

实在没办法了,输入了个1000000 100000试了试...发现大概用了二十秒才初始化结束。突然顿悟原来这里还有个log的操作在做鬼

void build(int p, int l, int r){
t[p].l = l;t[p].r = r;
if(l == r){
for(int i=1;i<=32;i++){
//这一句复杂度是(log2e18),大概是60
//即使反过来换成mul(f[i],l),也会有20左右的大小,根本无法承受
t[p].s[i] = mul(l, f[i]) % 2019;
}
t[p].res = t[p].s[28];
t[p].tag = 0;
return;
}
int mid = l + r >> 1;
build(p*2, l, mid);
build(p*2+1, mid+1, r);
for(int i=1;i<=32;i++){
t[p].s[i] = t[p*2].s[i] + t[p*2+1].s[i];
}
t[p].res = t[p].s[28];
t[p].tag = 0;
}

好了,这次应该知道怎么优化了,题目数据给出 A 序列初值为 1...n 也是有道理的,否则这里的复杂度怎么也优化不掉的

总时间复杂度\(O(32n+32q\log n\))

AC截图

这个时限(4s),空间(1G),感觉真是刚刚好

下面是AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
typedef unsigned long long ull;
const int N = 1000010;
ull U[5] = {
314882150829468584,
427197303358170108,
1022292690726729920,
1698479428772363217,
2006101093849356424
};
ull mod = 2009731336725594113;
unordered_map<ull, int> mp;
ull f[35],a[N][35];
int g[35][35];
ull mul(ull a, ull b){
ull res = 0;
for(;b;b>>=1){
if(b & 1) res = (res + a) % mod;
a = (a + a) % mod;
}
return res;
}
void getConvert(){
queue<ull> q;
int id = 1;
for(int i=0;i<5;i++)q.push(U[i]), mp[U[i]] = id++;
while(q.size()){
ull x = q.front();q.pop();
f[mp[x]] = x;
for(int i=0;i<5;i++){
ull t = mul(x, U[i]);
if(mp[t]) {
continue;
}
mp[t] = id++;
q.push(t);
}
}
for(int i=1;i<=32;i++){
for(int j=i;j<=32;j++){
g[i][j] = g[j][i] = mp[mul(f[i], f[j])];
}
}
}
int tmp[33];
int n, q;
struct SegTree{
int l,r;
int s[33];//维护它,但是它后期可能还会变
int res;
int tag;
// 转移函数,target表示乘 f[target]
void convert(int target){
for(int i=1;i<=32;i++){
tmp[i] = s[g[i][target]];
}
for(int i=1;i<=32;i++){
s[i] = tmp[i];
}
res = s[28];
if(tag == 0) tag = target;
else tag = g[tag][target];
}
}t[N<<2];
/*
下面初始res取 s[28]的原因是32个数字中第28个是 1
*/
void build(int p, int l, int r){
t[p].l = l;t[p].r = r;
if(l == r){
for(int i=1;i<=32;i++){
t[p].s[i] = a[l][i] % 2019;
}
t[p].res = t[p].s[28];
t[p].tag = 0;
return;
}
int mid = l + r >> 1;
build(p*2, l, mid);
build(p*2+1, mid+1, r);
for(int i=1;i<=32;i++){
t[p].s[i] = t[p*2].s[i] + t[p*2+1].s[i];
}
t[p].res = t[p].s[28];
t[p].tag = 0;
}
void pushdown(int p){
if(t[p].tag){
t[2*p].convert(t[p].tag);
t[2*p+1].convert(t[p].tag);
t[p].tag = 0;
}
}
int ask(int p, int l, int r){
if(t[p].l >= l && t[p].r <= r){
return t[p].res;
}
pushdown(p);
int mid = t[p].l + t[p].r >> 1;
int res = 0;
if(mid >= l) res = ask(p*2, l, r);
if(mid < r) res = res + ask(p*2+1, l, r);
return res;
}
void change(int p, int l, int r, int k){
if(t[p].l >= l && t[p].r <= r){
t[p].convert(k+1);
return;
}
pushdown(p);
int mid = t[p].l + t[p].r >> 1;
if(mid >= l) change(p*2, l, r, k);
if(mid < r) change(p*2+1, l, r, k);
for(int i=1;i<=32;i++) t[p].s[i] = t[p*2].s[i] + t[p*2+1].s[i];
t[p].res = t[p].s[28];
}
int main(){
getConvert();
scanf("%d%d", &n, &q);
// 由于序列是1...n,所以可以直接用加法递推
for(int i=1;i<=n;i++){
for(int j=1;j<=32;j++){
if(i == 1) a[i][j] = f[j] % mod;
else a[i][j] = (f[j] + a[i-1][j]) % mod;
}
}
build(1, 1, n);
while(q--){
int l, r;
scanf("%d%d", &l, &r);
int res = ask(1, l, r);
printf("%d\n", res);
change(1, l, r, res % 5);
}
return 0;
}

最后膜一下500分的那位大佬,在没有oj的情况下可以一发AC实在是非人哉

感谢耐心指导我的cls(杭电WF选手)

附样例2输入数据

100 100
45 74
38 50
7 45
42 62
83 100
50 51
8 11
93 98
64 70
15 87
30 87
13 79
14 81
18 79
70 88
25 39
13 57
55 85
80 92
83 90
54 75
1 61
17 42
25 49
39 77
32 45
83 87
30 47
59 84
25 50
1 82
21 45
72 96
3 85
16 64
52 92
28 29
84 88
26 93
10 67
27 76
57 62
43 69
63 66
5 59
9 46
49 53
35 50
3 19
23 62
38 73
17 68
34 83
42 91
13 92
19 62
17 70
18 75
95 99
35 90
81 91
59 63
5 90
22 87
51 88
25 61
56 91
50 78
11 60
11 18
27 45
57 82
16 54
3 94
33 56
9 71
68 88
24 36
7 64
48 85
58 76
20 43
9 90
24 27
71 97
25 95
73 97
55 83
22 43
53 55
68 88
12 44
25 87
14 46
34 56
15 35
7 80
46 87
23 71
88 93

CCF计算机软件能力认证试题练习:201912-5 魔数的更多相关文章

  1. 【实(dou)力(bi)首(mai)发(meng)】第四次CCF软件能力认证题解

    这次的题总体上相对前三次偏简单.由于实力有限,就分析前四题.     试题编号:    201503-1 试题名称:    图像旋转 时间限制:    5.0s 内存限制:    256.0MB 问题 ...

  2. 第四届CCF软件能力认证(CSP2015) 第五题(最小花费)题解

    [问题描述] C国共有$n$个城市.有$n-1$条双向道路,每条道路连接两个城市,任意两个城市之间能互相到达.小R来到C国旅行,他共规划了$m$条旅行的路线, 第$i$条旅行路线的起点是$s_i$,终 ...

  3. 第四届CCF软件能力认证

    1.图像旋转 问题描述 旋转是图像处理的基本操作,在这个问题中,你需要将一个图像逆时针旋转90度. 计算机中的图像表示可以用一个矩阵来表示,为了旋转一个图像,只需要将对应的矩阵旋转即可. 输入格式 输 ...

  4. 第三届CCF软件能力认证

    1.门禁系统 问题描述 涛涛最近要负责图书馆的管理工作,需要记录下每天读者的到访情况.每位读者有一个编号,每条记录用读者的编号来表示.给出读者的来访记录,请问每一条记录中的读者是第几次出现. 输入格式 ...

  5. 第二届CCF软件能力认证

    1. 相邻数对 问题描述 给定n个不同的整数,问这些数中有多少对整数,它们的值正好相差1. 输入格式 输入的第一行包含一个整数n,表示给定整数的个数. 第二行包含所给定的n个整数. 输出格式 输出一个 ...

  6. 第一届CCF软件能力认证

    1.相反数 问题描述 有 N 个非零且各不相同的整数.请你编一个程序求出它们中有多少对相反数(a 和 -a 为一对相反数). 输入格式 第一行包含一个正整数 N.(1 ≤ N ≤ 500). 第二行为 ...

  7. 第六届CCF软件能力认证

    1.数位之和 问题描述 给定一个十进制整数n,输出n的各位数字之和. 输入格式 输入一个整数n. 输出格式 输出一个整数,表示答案. 样例输入 20151220 样例输出 13 样例说明 201512 ...

  8. 第五届CCF软件能力认证

    1.数列分段 问题描述 给定一个整数数列,数列中连续相同的最长整数序列算成一段,问数列中共有多少段? 输入格式 输入的第一行包含一个整数n,表示数列中整数的个数. 第二行包含n个整数a1, a2, … ...

  9. C1 能力认证——Web进阶

    C1 能力认证--Web进阶 DOM节点操作-上 名称 描述 getElementById() 获取带有指定id的节点 getElementsByTagName() 获取带有指定标签名的节点集合 qu ...

随机推荐

  1. SpringCloud --服务调用Feign

    介绍 服务间通信简介 一个系统可以由不同的微服务构成,比如一个电商系统可以由订单服务.商品服务.用户服务等共同组成. 这些服务相互独立,但又相互依赖.由于它们相互依赖,所以需要通过通信的方式来进行相互 ...

  2. ForwardProcess

    共享式网络中,发送的报文所有设备都可以收到 0.0.0.0/0 缺省路由 可以到达任意网络 From WizNote

  3. MyBatis中id回填的两种方式

    在一种场景下需要刚刚插入数据的ID,如果数据少可以先看数据库,记下ID,但数据很多,假设一万个用户并发,每个用户都插入自己的ID,就很难记下来. 下面给定一个场景: 1 User user = new ...

  4. LeetCode876 链表的中间结点

    给定一个带有头结点 head 的非空单链表,返回链表的中间结点. 如果有两个中间结点,则返回第二个中间结点. 示例 1: 输入:[1,2,3,4,5] 输出:此列表中的结点 3 (序列化形式:[3,4 ...

  5. JVM 源码分析(四):深入理解 park / unpark

    前言 Parker 源码调试与分析 park/unpark 原理总结 补充:jstack 命令和 kill 命令 前言 熟悉 Java 并发包的人一定对 LockSupport 的 park/unpa ...

  6. 【Java】流程控制 - 顺序结构、 选择(分支)结构(单分支、双分支、多分支、嵌套)、循环结构(for、while、do...while)、跳转语句(break、continue)

    流程控制语句结构 文章目录 流程控制语句结构 一. 顺序结构 1. 输出语句 2. 输入语句 3.code 二.复合语句 三. 分支结构 1. 条件判断 1.单分支结构 2.双分支结构 3.多分支结构 ...

  7. 【Java】标识符

    一.标识符 文章目录 一.标识符 1.标识符的命名规则 2.关键字.保留字.特殊值 3.code Java 对各种变量.方法和类等要素命名时使用的字符序列称为标识符.简单的说,凡是程序员自己命名的部分 ...

  8. /usr/bin/ld: cannot find -lc

    yum install glibc-static [root@test chkrootkit-0.50]# make sensecc -static -o strings-static strings ...

  9. 【Linux】在文件的指定位置插入数据

    今天遇到一个似乎很棘手的问题,要在文件的中间,插入几条配置 这里就以my.cnf这个文件为例 1 [mysqld] 2 datadir=/var/lib/mysql 3 socket=/var/lib ...

  10. go语言循环变量

    阅读go语言圣经第五章第六节介绍到了捕获迭代变量 package main import ( "fmt" ) func main() { var lis []func() for ...