2020721水题大赛题解

A. A(异或粽子)

题目描述

  • 小粽是一个喜欢吃粽子的好孩子。今天她在家里自己做起了粽子。
  • 小粽面前有 n 种互不相同的粽子馅儿,小粽将它们摆放为了一排,并从左至右编号为 1 到 n 。第 i 种馅儿具有一个非负整数的属性值 \(a_i\) 。每种馅儿的数量都足够多,即小粽不会因为缺少原料而做不出想要的粽子。小粽准备用这些馅儿来做出 k 个粽子。
  • 小粽的做法是:选两个整数 l,r ,满足 \(1\le l\le r\le n\),将编号在 \([l, r]\) 范围内的所有馅儿混合做成一个粽子,所得的粽子的美味度为这些粽子的属性值的异或和。(异或就是我们常说的 xor 运算,即 C/C++ 中的 ^ 运算符或 Pascal 中的 xor 运算符)
  • 小粽想品尝不同口味的粽子,因此它不希望用同样的馅儿的集合做出一个以上的粽子。
  • 小粽希望她做出的所有粽子的美味度之和最大。请你帮她求出这个值吧!

输入格式

  • 从标准输入读入数据。
  • 第一行两个正整数 n,k ,表示馅儿的数量,以及小粽打算做出的粽子的数量。
  • 接下来一行为 n 个非负整数,第 i 个数为 \(a_i\) ,表示第 i 个粽子的属性值。

输出格式

  • 输出到标准输出。
  • 输出一行一个整数,表示小粽可以做出的粽子的美味度之和的最大值。

样例输入

3 2
1 2 3

样例输出

6

样例说明

  • 小粽可以选取 \([3, 3], [1, 2]\) 两个区间的馅儿做成粽子,两个粽子的美味度都为 3,和为 6 。可以证明不存在比这更优的方案。

数据范围与提示

测试点 \(n\) \(k\)
\(1-8\) \(\le 10^3\) \(\le 10^3\)
\(9-12\) \(\le 5\times 10^5\) \(\le 10^3\)
\(13-16\) \(\le 10^3\) \(\le 2\times 10^5\)
\(17-20\) \(\le 5\times 10^5\) \(\le 2\times 10^5\)
  • 对于所有的输入数据都满足:\(1\le n\le 5 \times 10^5, 1\le k\le \min\left \{ \frac{n(n-1)}{2} ,2\times 10^5\right \}, 0\le a_i\le 4294967295\)

Solve

  • 区间异或和可以转换成对两端点求解,要求出前k大的数对,把这些数对分成 n+1 类,每一类选出最大的压入大根堆中,循环k次取出堆顶,记录答案,并将堆顶所属类别的下一大的数压入队中。
  • 查询第k大的值就是进行二分。
  • 每一类是分别以j结尾的数对,需要在j之前找到一个i使得i~j之间异或和第k大,必须保证 i<j ,这样就得用到可持久化trie树,如果不保证 i<j 一个结果就会分别在i类和j重复计算,我们可以直接取前2k大的数,最后答案减半即可。

Code

#include <queue>
#include <cstdio>
#define ll long long
using namespace std;
const int N = 5e5 + 5;
struct Node {
    int x, k;
    ll w;
    Node() {}
    Node(ll a, int b, int c) {
        w = a, x = b, k = c;
    }
    bool operator < (const Node &b) const {
        return w < b.w;
    }//重载运算符,堆顶是w值最大的
};
priority_queue<Node> q;
int t[N*33][2], tot = 1, n, k, w[N*33];//注意trie树的空间
ll s[N], ans;//本题需要unsigned int 这里直接开long long
void Add(ll x) {
    int p = 1;
    for (int i = 31; i >= 0; --i) {
        bool y = x>>i & 1;
        if (!t[p][y]) t[p][y] = ++tot;
        p = t[p][y];
        ++w[p];
    }
}
ll Ask(ll x, int k) {
    int p = 1;
    ll s = 0;
    for (int i = 31; i >= 0; --i) {
        bool y = x>>i & 1;
        if (w[t[p][!y]] >= k) y ^= 1, s |= (ll)1 << i;
        else k -= w[t[p][!y]];
        p = t[p][y];
    }
    return s;
}
int main() {
    scanf("%d%d", &n, &k);
    k <<= 1;
    Add(0LL);//需要将s[0]是值加入tire树
    for (int i = 1; i <= n; ++i) {
        scanf("%lld", &s[i]);
        s[i] ^= s[i-1];//异或前缀和
        Add(s[i]);
    }
    for (int i = 0; i <= n; ++i)
        q.push(Node(Ask(s[i], 1), i, 1));
    while (k--) {
        Node a = q.top(); q.pop();
        ans += a.w;
        q.push(Node(Ask(s[a.x], a.k + 1), a.x, a.k + 1));
    }
    printf("%lld\n", ans >> 1);
    return 0;
}

B. B(tree)

题目描述

  • 给你一棵 n 个点的树,边长为 1,有 m 次询问,每次询问两个节点 x,y 求从 x 随机游走到 y 的期望长度。游走定义为从当前节点出发等概率随机走向相连的其他节点。答案\(\mod 1e9+7\)

输入格式

  • 第一行两个整数 n,m,接下来 n-1 行每行两个整数代表树上的一条边。
  • 接下来 m 行,每行两个整数表是询问的 x,y 。

输出格式

  • 一共 m 行,每行一个整数表示对应询问的答案。

样例输入

4 2
1 2
2 3
3 4
1 4
3 4

样例输出

9
5

数据范围与提示

  • 对于20%数据 \(n,m\le 10\)
  • 对于40%数据 \(n,m\le 1000\)
  • 另有20%保证树是一条链
  • 对于100%数据 \(n,m\le 10^5\)

Solve

  • 一道期望题,考试时按题目rand模拟,0分,考完加强了一下代码,能拿20分呀。
    正解感觉有点像DP
  • 题目中游走定义为从当前节点出发等概率随机走向相连的其他节点,则从 x 走向每个节点的概率为\(\frac{1}{deg[x]}\)deg[x]为 x 的度,即连接的节点数。
  • f[x]表示从 x 走到 x 的父节点的期望长度
    f[x]就等于直接走到父节点的期望长度+走到每个子节点再走回自己再走父节点的期望长度

写成表达式
\(f[x]=\frac{1}{deg[x]} + \sum_{y|son of x} \frac{1+f[y]+f[x]}{deg[x]}\)
化简可得
\(f[x]=deg[x] + \sum_{y|son of x} f[y]\)

  • g[x]表示从 x 的父节点走到 x 的期望长度
    g[x]就等于直接走到x的期望长度+走到父节点的父节点再走回来的期望长度+走到父节点的其他子节点在走回父节点再走到x的期望长度

写成表达式
\(g[x]=\frac{1}{deg[fa[x]]}+\frac{1+g[x]+g[fa[x]]}{deg[fa[x]]}+\sum_{y|other son of fa[x]}\frac{1+g[x]+f[y]}{deg[fa[x]]}\)
化简可得
\(g[x]=deg[fa[x]]+g[fa[x]]+\sum_{y|other son of fa[x]}f[y]\)
由f[x]的式子可得
\(g[x]=f[fa[x]]-f[x]+g[fa[x]]\)

  • f[x]和g[x]都求出来了,在进行树上前缀和,x到y的期望长度就是
    \(sumf[x]-sumf[lca]+sumg[y]-sumg[lca]|lca=Lca(x,y)\)

Code

#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
const int N = 1e5 + 5, M = 1e9 + 7;
struct Side {
    int t, next;
}e[N<<1];
int head[N], tot;
void Add(int x, int y) {
    e[++tot] = (Side) {y, head[x]};
    head[x]= tot;
}
int n, m, f[N], g[N], fa[N][21], d[N], dep[N];
void Dfs1(int x) {//预处理出每个节点的深度,父节点,f值
    dep[x] = dep[fa[x][0]] + 1;
    f[x] = d[x];
    for (int i = head[x]; i; i = e[i].next) {
        int y = e[i].t;
        if (y == fa[x][0]) continue;
        fa[y][0] = x;
        Dfs1(y);
        f[x] = (f[x] + f[y]) % M;
    }
}
void Dfs2(int x) {//预处理出g值
    for (int i = head[x]; i; i = e[i].next) {
        int y = e[i].t;
        if (y == fa[x][0]) continue;
        g[y] = ((ll)f[x] - f[y] + g[x]) % M;
        Dfs2(y);
    }
}
void Dfs3(int x) {//预处理出f,g的树上前缀和
    for (int i = head[x]; i; i = e[i].next) {
        int y = e[i].t;
        if (y == fa[x][0]) continue;
        f[y] = (f[x] + f[y]) % M;
        g[y] = (g[x] + g[y]) % M;
        Dfs3(y);
    }
}
int Jump(int x, int k) {
    int a = 0;
    while (k) {
        if (k & 1) x = fa[x][a];
        k >>= 1; ++a;
    }
    return x;
}
int Lca(int x, int y) {//倍增求Lca
    if (dep[x] < dep[y]) swap(x, y);
    x = Jump(x, dep[x] - dep[y]);
    if (x == y) return x;
    for (int i = 20; i >= 0; --i)
        if (fa[x][i] != fa[y][i])
            x = fa[x][i], y = fa[y][i];
    return fa[x][0];
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i < n; ++i) {
        int x, y;
        scanf("%d%d", &x, &y);
        Add(x, y); Add(y, x);
        ++d[x]; ++d[y];
    }
    Dfs1(1); 
    Dfs2(1); 
    f[1] = 0;//f[1] = g[1] = 0,因为1没有父节点
    Dfs3(1);
    for (int i = 1; i <= 20; ++i)
        for (int x = 1; x <= n; ++x)
            fa[x][i] = fa[fa[x][i-1]][i-1];//Lca的预处理
    while (m--) {
        int x, y;
        scanf("%d%d", &x, &y);
        int lca = Lca(x, y);
        printf("%lld\n", ((ll)f[x] - f[lca] + g[y] - g[lca]) % M);
    }
    return 0;
}

C. C(四叶草)

题目描述

  • SueJane热衷于找寻四叶草,并坚信可以带来好运。
  • SueJane现在有 n 株四叶草,她给这些四叶草按照好看的程度设定了排名。不存在两株四叶草的排名相同,因此,这个排名序列是一个 1 ~ n 的排列。
  • SueJane现在想把这些四叶草按一定的位置排成一排。设排在第 i 的位置的四叶草排名为 $p_ i $,那么对于SueJane而言,如果对于 \(\forall i, 1\le i < n\),都能找到一个 j,满足 \(i< j\le n\) 且 $ | p_ i - p _ j|$(即 后面的位置有一株四叶草排名与 i 相差 1),那么这个序列看起来就比较美观。比如一个序列 1, 4, 3, 2 是美观的,而 2, 4, 3, 1 就不美观(3 后面不存在符合要求的数)。
  • 此外,SueJane还对一些位置做出了限制,内容为规定某位置上的四叶草排名必须为 \(k_i\)
  • 现在她想知道,有多少个序列是满足条件的。请你求出满足条件的序列个数 \(\mod 998244353\) ,不然就会被SueJane派去找四叶草。

输入格式

  • 第一行为两个整数 n, K,分别表示SueJane一共有 n 株四叶草,并且给出了 K 条限制。
  • 接下来 K 行,每行 2 个数 x,y,表示第 x 个位置上的四叶草排名必须为 y 。

输出格式

  • 一行一个整数表示答案。

样例输入

4 2
1 1
2 4

样例输出

2

样例解释

  • 两个限制分别要求 \(p_1 = 1, p _2 = 4\)
  • 符合条件的序列有 \(1,4,2,3\)\(1,4,3,2\),可以证明不存在其它符合条件的排列。

数据范围与提示

  • 10% 的数据满足 \(1\le n\le 10\)
  • 20% 的数据满足 \(1\le n\le 20\)
  • 30% 的数据满足 \(1\le n\le 500\)
  • 另有 10% 的数据满足 K=0;
  • 对于 100% 的数据,满足 \(1\le n\le 5000,0\le K\le 233\),限制条件中不存在同一个位置限制不同的数或不同的位置限制同一个数。

Solve

解法0(来自某学长课件)

辣鸡SueJane肯定不会造数据!限制条件都是互相矛盾的!
大力 puts("0");
得分:\(0pts\)
辣鸡SueJane造的数据最大的特点就是限制条件没有矛盾。所有的答案都不是0 。

  • 暴搜可以拿30,打表可以发现 k=0 的情况,答案是\(2^{n-1}\),再骗10分。

  • 根据题意我们可以知道最后一个位置是一个特殊的位置,因为他后面没有数,也就是没有限制
    我们考虑n个数(n>3)的前3个数:123,并假设这三个数没有限制,将4放在最后一位,可以想到这3个数一定是升序,反证一下,如果2在1前面,1后面就没有绝对值等于1的数(0不可选),运用数学归纳法可以知道不论几个数,都是升序排序,同理5到n这几个数就是逆序排序,这样只要保证每个数的相对顺序,前后两个序列就可以顺便排。

  • 定义f[i][j]为前面i个数,后面j个数,此时选到i+j的方案数,转移方式如下:

    • i+j限制为i,可以放在前面序列,f[i][j] += f[i-1][j]

    • i+j限制为n-j+1,可以放在后面的序列,f[i][j] += f[i][j-1]

    • i+j限制不为以上两种,不可以转移

    • i+j没有限制,f[i][j] += f[i-1][j] + f[i][j-1]|i,j!=0

Code

#include <cstdio>
using namespace std;
const int N = 5e3 + 5, M = 998244353;
int n, k, a[N], f[N][N], ans;
int main() {
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= k; ++i) {
        int x, y;
        scanf("%d%d", &x, &y);
        a[x] = y;
    }
    f[0][0] = 1;
    for (int i = 0; i < n; ++i)
        for (int j = 0; j + i < n; ++j)
            if (a[i+j]) {
                if (a[i+j] == i) f[i][j] = (f[i][j] + f[i-1][j]) % M;
                else if (a[i+j] == n - j + 1) f[i][j] = (f[i][j] + f[i][j-1]) % M;
            }
            else {
                if (i) f[i][j] = (f[i][j] + f[i-1][j]) % M;
                if (j) f[i][j] = (f[i][j] + f[i][j-1]) % M;
            }
    if (a[n]) return printf("%d\n", f[a[n]-1][n-a[n]]), 0;
    for (int i = 0; i < n; ++i)
        ans = (ans + f[i][n-1-i]) % M;
    printf("%d\n", ans);
    return 0;
}

D. D(Kefa and Watch)

题目描述

  • 给定一个长度为 n 的仅包含 0 - 9 的数字的数字串,要求实现两个操作
    1.给定 l,r,c,将 \([l,r]\) 上的数字全部改为 c
    2.给定 l,r,c,判断区间 \([l,r]\)是否有长度为 c 的循环节
  • 说明一下题目中循环节的定义
    • 假设我们有一个这样的串: 010101010
      那么2,4,6,8都是该串的循环节(即最后一个循环可以是不满的)

输入格式

  • 第一行为两个整数 n, m,n 表示数字串的长度,m 表示操作个数
  • 第二行为长度为 n 的仅包含 0 - 9 的数字串
  • 下面 m 行,每行有四个数字 t,l,r,c, t = 1 时为操作1, t = 2 时为操作2

输出格式

  • 对于每个 t = 2 的操作,输出一行YES或NO

样例输入1

3 3
112
2 2 3 1
1 1 3 8
2 1 2 1

样例输出1

NO
YES

样例输入2

6 5
334934
2 2 5 2
1 4 4 3
2 1 6 3
1 2 3 8
2 3 6 1

样例输出2

NO
YES
NO

数据范围与提示

  • 对于30%的数据 \(m\le 5e3\)
  • 对于100%的数据 \(m\le 1e5\)
  • 保证数据有一定梯度

Solve

  • 方法1:线段树维护Hash值

  • 方法2:直接上库中的函数memset,memcmp,注意memcmp比较函数的返回结果,如果相同会返回0

Code1

#include <cstdio>
#define ull unsigned long long
#define ls t[rt].l
#define rs t[rt].r
using namespace std;
const int N = 1e5 + 4, base = 131;
struct Tree {
    int l, r, s, lazy;
    ull h;
}t[N<<2];
int n, m, k;
ull p[N], b[N];
char a[N];
void Pushup(int rt) {
    t[rt].h = t[rt<<1].h * p[t[rt<<1|1].s] + t[rt<<1|1].h;
}
void Build(int rt, int l, int r) {
    ls = l, rs = r;
    t[rt].s = r - l + 1;
    t[rt].lazy = -1;
    if (l == r) return t[rt].h = a[l], void();
    int mid = l+r >> 1;
    Build(rt << 1, l, mid); 
    Build(rt << 1 | 1, mid + 1, r);
    Pushup(rt);
}
void Update(int rt, int w) {
    t[rt].lazy = w;
    t[rt].h = b[t[rt].s] * w;
}
void Pushdown(int rt) {
    if (t[rt].lazy == -1) return;
    Update(rt << 1, t[rt].lazy);
    Update(rt << 1 | 1, t[rt].lazy);
    t[rt].lazy = -1;
}
void Change(int rt, int l, int r, int w) {
    if (ls == l && rs == r) return Update(rt, w);
    Pushdown(rt);
    int mid = ls + rs >> 1;
    if (r <= mid) Change(rt << 1, l, r, w);
    else if (l > mid) Change(rt << 1 | 1, l, r, w);
    else Change(rt << 1, l, mid, w), Change(rt << 1 | 1, mid + 1, r, w);
    Pushup(rt);
}
ull Ask(int rt, int l, int r) {
    if (l > r) return 0;
    if (ls == l && rs == r) return t[rt].h;
    Pushdown(rt);
    int mid = ls + rs >> 1;
    if (r <= mid) return Ask(rt << 1, l, r);
    else if (l > mid) return Ask(rt << 1 | 1, l, r);
    else return Ask(rt << 1, l, mid) * p[r-mid] + Ask(rt << 1 | 1, mid + 1, r);
}
int main() {
    scanf("%d%d%d%s", &n, &m, &k, a + 1);
    m += k;
    p[0] = 1;
    for (int i = 1; i <= n; ++i) {
        p[i] = p[i-1] * base;
        b[i] = b[i-1] * base + 1;//b[i]是长为i的单位1
    }
    Build(1, 1, n);
    while (m--) {
        int od, l, r, c;
        scanf("%d%d%d%d", &od, &l, &r, &c);
        if (od == 1) Change(1, l, r, c + '0');
        else puts(Ask(1, l, r - c) == Ask(1, l + c, r) ? "YES" : "NO");
    }
    return 0;
}

Code2

#include <cstdio>
#include <cstring>
using namespace std;
const int N = 2e5 + 5;
int n, m;
char a[N];
int main() {
    scanf("%d%d%s", &n, &m, a + 1);
    while (m--) {
        int od, l, r, c;
        scanf("%d%d%d%d", &od, &l, &r, &c);
        if (od == 1) memset(a + l, c + '0', r - l + 1);
        else puts(memcmp(a + l, a + l + c, r - l + 1 - c) ? "NO" : "YES");
    }
    return 0;
}
posted @ 2020-07-26 06:50  Shawk  阅读(195)  评论(0编辑  收藏  举报