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

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截图
image.png

这个时限(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
posted @ 2020-04-21 17:15  kpole  阅读(983)  评论(3编辑  收藏  举报