第四届“传智杯”全国大学生IT技能大赛 民间题解

更好的阅读体验?

比赛传送

因为本人参加的是洛谷的同步赛,可能题目顺序与正式赛选手不同?

今年题目难度普遍偏低。只有 D,F 还好。

有些题就不粘代码了。

A

按题目给的公式计算即可。注意应在最后的答案中去掉小数部分。

B

按照题意模拟即可。注意答案要与 \(0\)\(\max\)

C

按照题意模拟即可。注意应该先做乘法在做除法,或者把后面的值用 doublefloat 数据类型的变量存储,并在最后去掉小数部分。

时间复杂度 \(\mathcal O(n)\)

D

算法一

\(x \oplus y = p\) ,则 \(x \oplus p = y\)。因此我们要找有多少 \(p\) 满足 \(x \oplus p < x\)

考虑到 \(x \oplus y\) 的值最大为 \(2^{21} - 1\) ,因此我们用线性筛筛出 \(\le 2.1 \times 10^6\) 的所有素数,然后把它们插入一个 01Trie

然后我们要求的答案在 01Trie 中其实是一个个字数内的数的个数的和。

在遍历 \(x\) 的每一个二进制位的过程中,设该二进制位为 \(c\),当前节点编号为 \(u\)\(tr_{u,0/1}\) 表示下一个分别是 \(0,1\) 的位置的编号。

如果 \(c=1\),那个 \(tr_{u,0}\) 内的所有子树一定合法,我们加上它子树内的数的个数,然后让 \(u \to rt_{u,1}\),即进入右子树。

如果 \(c=0\),那个子树都不能造成贡献,我们直接进入 \(tr_{u,0}\),即左子树。

如果某个非起始位置 \(u=0\) ,直接退出即可。

时间复杂度为 \(\mathcal O((Cnt + m) \log V)\),其中 \(Cnt\) 是质数个数,\(V\) 是值域。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2.1e6 + 10;

int T, x;
int ans[MAXN];
int prim[MAXN], Cnt = 0;
bool vis[MAXN], flag[MAXN];

void Init() {
    int M = 2100000;
    for(int i = 2; i <= M; ++i) {
        if(!vis[i]) prim[++Cnt] = i;
        for(int j = 1; j <= Cnt && i * prim[j] <= M; ++j) {
            vis[i * prim[j]] = true;
            if(i % prim[j] == 0) break;
        }
    }
}

namespace Trie {
    int tr[MAXN << 2][2], node_num = 0, siz[MAXN << 2];
    void Insert(int x) {
        int u = 0;
        siz[0] ++;
        for(int j = 21; j >= 0; --j) {
            int c = (x >> j) & 1;
            if(!tr[u][c]) tr[u][c] = ++node_num;
            u = tr[u][c];
            siz[u] ++;
        }
    }
    int Query(int x) {
        int u = 0, res = 0;
        for(int j = 21; j >= 0; --j) {
            int c = (x >> j) & 1;
            if(c) {
                res = res + siz[tr[u][c]];
                u = tr[u][c ^ 1];
            } else {
                u = tr[u][c];
            }
            if(!u) return res;
        }
        return res;
    }
}

int main() {
    Init();
    for(int i = 1; i <= Cnt; ++i) {
        Trie::Insert(prim[i]);
    }
    scanf("%d", &T);
    while(T--) {
        scanf("%d", &x);
        int res = Trie::Query(x);
        printf("%d\n", res);
    }
    return 0;
}

算法二

考虑上面那个问题的本质。

其实问题可以再次转化成有多少 \(p\) 满足:如果设 \(x\)\(p\) 第一个不同的位置 \(i\)\(x\)\(i\) 这一位上为 \(1\)

进一步分析可以发现,如果 \(x\) 的第 \(i\) 位为 \(1\),那么所有 \(2^{i} \le p < 2^{i+1}\) 的质数都是合法的。

这其实和 \(p\) 的最高位数有关,按照这个预处理个数即可。

总复杂度 \(\mathcal O((Cnt + m) \log V)\)

#include<bits/stdc++.h> // 这个代码虽然没交但应该没有问题
using namespace std;
const int MAXN = 2.1e6 + 10;

int T, x;
int ans[MAXN], a[200];
int prim[MAXN], Cnt = 0;
bool vis[MAXN], flag[MAXN];

int read() {
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
    return f ? -s : s;
}

void Init() {
    int M = 2100000;
    for(int i = 2; i <= M; ++i) {
        if(!vis[i]) prim[++Cnt] = i;
        for(int j = 1; j <= Cnt && i * prim[j] <= M; ++j) {
            vis[i * prim[j]] = true;
            if(i % prim[j] == 0) break;
        }
    }
}

int main() {
    Init();
    for(int i = 1; i <= Cnt; ++i) {
        int x = prim[i];
        int lst, cnt = 0;
        while(x) {
            if(x & 1) lst = cnt;
            x >>= 1, ++ cnt;
        }
        a[lst] ++;
    }
    cin >> T;
    while(T--) {
        int Ans = 0;
        cin >> x;
        for(int i = 0; i <= 21; ++i) 
            if(x & (1 << i)) Ans += a[i];
        printf("%d\n", Ans);
    }
    return 0;
}

E

题意可以转化成有 \(k\) 个可重集,每次向里面插入 \(p\) 个数对 \((a,b)\) ,每个数对 \(a,b\) 表示向第 \(a\) 个集合中插入一个值为 \(b\) 的数。

查询的话就是第 \(x\) 个集合中在 \([y_{\min},y_{\max}]\) 内的数有多少个。

发现 \(k,n\) 都很小,对于每个集合维护一个树状数组即可。

时间复杂度 \(\mathcal O(n \log n)\)

#include<bits/stdc++.h>
using namespace std;

int n, k, M = 1000;

struct Bit {
    int sum[1010];
    int lb(int x) { return x & -x; }
    void Modify(int x, int k) { for(; x <= M; x += lb(x)) sum[x] += k; }
    int Query(int x) { int res = 0; for(; x; x -= lb(x)) res += sum[x]; return res; }
}bit[1010];


int main() {
    cin >> n >> k;
    for(int i = 1, opt, p; i <= n; ++i) {
        cin >> opt >> p;
        if(opt == 1) {
            for(int j = 1, x, y; j <= p; ++j) {
                cin >> x >> y;
                bit[x].Modify(y, 1);
            }
        } else {
            int amax, amin;
            cin >> amin >> amax;
            int res = bit[p].Query(amax) - bit[p].Query(amin - 1);
            cout << res << "\n";
        }
    }
}

F

考虑利用 \(dfs\) 序转化到序列上。

考虑把深度当成主席树的值域维护,每个位置对应深度权值为 \(1\)

操作一可以直接用一个 lst 变量记录。

操作二就可以直接查询 \([dfn_x, dfn_x + siz_x - 1]\) 这段区间中 \(dep_{pre_i} \ge lst\) 的位置有多少。

注意特判一下一开始整个树都是绿的。

时间复杂度 \(\mathcal O(n \log n)\)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;

struct edge {
    int to, nxt;
}e[MAXN << 1];
int head[MAXN], num_edge = 1;

int n, m;
int root[MAXN];
int siz[MAXN], dfn[MAXN], pre[MAXN], dep[MAXN], Cnt = 0;

namespace Hjt {
    #define ls lson[now_]
    #define rs rson[now_]
    int node_num = 0, siz[MAXN << 5], lson[MAXN << 5], rson[MAXN << 5];
    void Insert(int &now_, int pre_, int l, int r, int pos, int k) {
        now_ = ++node_num;
        siz[now_] = siz[pre_] + k;
        ls = lson[pre_], rs = rson[pre_];
        if(l == r) return ;
        int mid = (l + r) >> 1;
        if(mid >= pos) Insert(ls, lson[pre_], l, mid, pos, k);
        else Insert(rs, rson[pre_], mid + 1, r, pos, k);
    }
    int Query(int now_, int pre_, int L, int R, int l, int r) {
        if(L <= l && r <= R) return siz[now_] - siz[pre_];
        int mid = (l + r) >> 1, ans = 0;
        if(mid >= L) ans += Query(ls, lson[pre_], L, R, l, mid);
        if(mid < R) ans += Query(rs, rson[pre_], L, R, mid + 1, r);
        return ans;
    }
}

void add_edge(int from, int to) { e[++num_edge] = (edge){to, head[from]}, head[from] = num_edge; }

void dfs(int u, int fa) {
    dep[u] = dep[fa] + 1;
    dfn[u] = ++Cnt, pre[Cnt] = u, siz[u] = 1;
    for(int i = head[u]; i; i = e[i].nxt) {
        int v = e[i].to;
        if(v == fa) continue;
        dfs(v, u);
        siz[u] += siz[v];
    } 
}

int main() {
    cin >> n >> m;
    for(int i = 1, u, v; i < n; ++i) {
        cin >> u >> v;
        add_edge(u, v), add_edge(v, u);
    }
    dfs(1, 0);
    for(int i = 1; i <= n; ++i) {
        Hjt::Insert(root[i], root[i - 1], 1, n + 1, dep[pre[i]], 1);
    }
    for(int i = 1, opt, x, lst = n + 1; i <= m; ++i) {
        cin >> opt >> x;
        if(opt == 1) lst = x;
        else {
            int res = Hjt::Query(root[dfn[x] + siz[x] - 1], root[dfn[x] - 1], lst, n + 1, 1, n + 1);
            cout << res << "\n";
        }
    }
    return 0;
}

G

注意题目要求是第 \(x,y\) 个质数。

其实是一道诈骗题。

发现如果两个数满足相异或为 \(1\) 那么他们两个数的值必定只差 \(1\),而这样的质数对只有 \((2,3)\) 这一对,特判一下即可。

时间复杂度 \(\mathcal O(T)\)

posted @ 2021-12-19 18:32  Suzt_ilymtics  阅读(783)  评论(2编辑  收藏  举报