牛客练习赛47 题解

比赛传送门

正好跟cf时间撞上了,然而我太菜了cf的题做不出来,就来看了看牛客的题

真的好简单啊…

A DongDong破密码

题意:将一个01串 a a a(长度为 n n n)向右移动 m m m次,最后求出这 n + m − 1 n+m-1 n+m1位每一位的异或值,得到一个新串 b b b。(这个描述不太清楚,建议看原题有张图)现在给出串 b b b,请你求出原串 a a a

题解: b [ i ] b[i] b[i]的值其实就代表了 a [ i − m + 1... i ] a[i-m+1...i] a[im+1...i]这一段区间上的 1 1 1的个数的奇偶性。维护一个前缀和数组 s [ i ] s[i] s[i]表示 a a a串前 i i i位一共有多少个 1 1 1,那么比较 b [ i ] b[i] b[i] s [ i − 1 ] − s [ i − m ] s[i - 1]-s[i-m] s[i1]s[im]的奇偶性就可以知道第 i i i位是否是 1 1 1

#include 
#include 
#include 
#include 

template <typename T> inline void read(T& x) {
    int f = 0, c = getchar(); x = 0;
    while (!isdigit(c)) f |= c == '-', c = getchar();
    while (isdigit(c)) x = x * 10 + c - 48, c = getchar();
    if (f) x = -x;
}
template <typename T, typename... Args>
inline void read(T& x, Args&... args) {
    read(x); read(args...); 
}
template <typename T> void write(T x) {
    if (x < 0) x = -x, putchar('-');
    if (x > 9) write(x / 10);
    putchar(x % 10 + 48);
}

const int maxn = 1e6 + 7;

char str[maxn];
int ps[maxn], n, m;
int a[maxn], s[maxn];

int main() {
    read(n, m);
    scanf("%s", str + 1);
    for (int i = 1; i <= n + m - 1; ++i) ps[i] = str[i] - '0';
    for (int i = 1; i <= m; ++i) {
        if (ps[i] ^ (s[i - 1] & 1)) a[i] = 1, s[i] = s[i - 1] + 1;
        else a[i] = 0, s[i] = s[i - 1];
    }
    for (int i = m + 1; i <= n; ++i) {
        if (ps[i] ^ ((s[i - 1] - s[i - m]) & 1)) a[i] = 1, s[i] = s[i - 1] + 1; 
        else a[i] = 0, s[i] = s[i - 1];
    }
    for (int i = 1; i <= n; ++i) write(a[i]);
    return 0;
}
B DongDong认亲戚

题意略。并查集裸题。可以用map将人名映射到编号。代码略。

C DongDong跳一跳

题意:有 n n n根柱子,每根柱子都有一个高度和柱子上面鱼干的数量,一只猫开始的时候可以选择站在任意一根柱子上,每次跳跃不限长度而且只能从左向右跳跃,但只能跳到高度与当前所站高度差绝对值小于等于 m m m的柱子上。它会吃掉经过的柱子上的鱼干。请最大化这只猫能吃到的鱼干数量(最终不一定要落在第n根柱子上)。

算法1:设 f i f_i fi表示它从左往右跳到 i i i号柱子上,最多能吃到多少鱼干。转移显然

f i = max ⁡ f j + a i , 1 ≤ j < i , ∣ h j − h i ∣ ≤ m f_i=\max{f_j}+a_i,1\leq j<i,|h_j-h_i|\leq m fi=maxfj+ai,1j<i,hjhim

这样做时间复杂度是 O ( n 2 ) O(n^2) O(n2),不够优秀。
发现这里是一个二维数点,用线段树优化即可。
具体一点:我们可以求出 l i = max ⁡ ( 1 , h i − m ) , r i = h i + m l_i=\max(1,h_i-m),r_i=h_i+m li=max(1,him),ri=hi+m,那么我们必须从满足 l i ≤ h j ≤ r i l_i\leq h_j\leq r_i lihjri f j f_j fj中取一个最大的转移过来,这就是区间查最大值,用线段树维护一下就好。如果觉得空间有点紧,可以再离散化一下。
这个做法我没写,不过我看到有人写了,就说一下。

算法2:改变状态的定义。设 f ( i , j ) f(i,j) f(i,j)表示前 i i i个柱子中,跳到高度为 j j j的柱子上最多能吃多少。那么
f ( i , j ) = max ⁡ f ( i − 1 , k ) , l i ≤ k ≤ r i f(i,j)=\max{f(i-1,k)},l_i\leq k\leq r_i f(i,j)=maxf(i1,k),likri

这时候转移就变成了真正的区间最大值查询,你还可以用线段树优化,但也可以不用,因为这个区间长度是 O ( m ) O(m) O(m)的。然后我们发现 f ( i , ∗ ) f(i,*) f(i,)都是从 f ( i − 1 , ∗ ) f(i-1,*) f(i1,)转移的,所以 i i i这一维可以扔掉,那么空间复杂度就可以接受了。

#include 
#include 
#include 
#include 

template <typename T> inline void read(T& x) {
    int f = 0, c = getchar(); x = 0;
    while (!isdigit(c)) f |= c == '-', c = getchar();
    while (isdigit(c)) x = x * 10 + c - 48, c = getchar();
    if (f) x = -x;
}
template <typename T, typename... Args>
inline void read(T& x, Args&... args) {
    read(x); read(args...); 
}
template <typename T> void write(T x) {
    if (x < 0) x = -x, putchar('-');
    if (x > 9) write(x / 10);
    putchar(x % 10 + 48);
}
template <typename T> inline void writeln(T x) { write(x); puts(""); }
template <typename T> inline bool chkmin(T& x, const T& y) { return y < x ? (x = y, true) : false; }
template <typename T> inline bool chkmax(T& x, const T& y) { return x < y ? (x = y, true) : false; }

typedef long long LL;

const int maxx = 1e6 + 7;
const int maxn = 2e5 + 7;

int h[maxn], a[maxn];
LL f[maxx];
int n, m, mh;

int main() {
    read(n, m);
    for (int i = 1; i <= n; ++i) read(h[i], a[i]), chkmax(mh, h[i]);
    LL ans = 0;
    for (int i = 1; i <= n; ++i) {
        int l = std::max(1, h[i] - m), r = std::min(h[i] + m, mh);
        LL *j = std::max_element(f + l, f + r + 1);
        f[h[i]] = *j + a[i];
        chkmax(ans, f[h[i]]);
    }
    writeln(ans);
    return 0;
}

看到有很多人写这个做法的时候是倒着枚举 i i i的,事实上不需要吧?…因为这里只要高度差的绝对值在 m m m以内,所以从左往右还是从右往左都一样吧。

D DongDong坐飞机

题意: n n n个点, m m m条有向边,有边权。你要从 1 1 1走到 n n n,并且途中有 k k k次机会可以将一条边的代价减半,求最小代价。

题解:分层图最短路模板题。建 n ( k + 1 ) n(k+1) n(k+1)个点,结点 ( i , j ) (i,j) (i,j)表示你走到原图中的点 i i i并且剩余 j j j次减半机会。那么我们就是要从 ( 1 , k ) (1,k) (1,k)走到 ( n , ∗ ) (n,*) (n,)。对于原图中的一条边 ( u , v , w ) (u,v,w) (u,v,w),我们枚举剩余 i i i次减半机会,连边 ( ( u , i ) , ( v , i − 1 ) , w / 2 ) ((u,i),(v,i-1),w/2) ((u,i),(v,i1),w/2)表示使用减半机会,再连边 ( ( u , i ) , ( v , i ) , w ) ((u,i),(v,i),w) ((u,i),(v,i),w)表示没使用减半机会。然后跑最短路即可。

我写的是zkw线段树优化dijkstra求最短路。

#include 
#include 
#include 
#include 

template <typename T> inline void read(T& x) {
    int f = 0, c = getchar(); x = 0;
    while (!isdigit(c)) f |= c == '-', c = getchar();
    while (isdigit(c)) x = x * 10 + c - 48, c = getchar();
    if (f) x = -x;
}
template <typename T, typename... Args>
inline void read(T& x, Args&... args) {
    read(x); read(args...); 
}
template <typename T> void write(T x) {
    if (x < 0) x = -x, putchar('-');
    if (x > 9) write(x / 10);
    putchar(x % 10 + 48);
}
template <typename T> inline void writeln(T x) { write(x); puts(""); }
template <typename T> inline bool chkmin(T& x, const T& y) { return y < x ? (x = y, true) : false; }
template <typename T> inline bool chkmax(T& x, const T& y) { return x < y ? (x = y, true) : false; }

typedef long long LL;

const int maxn = 11e4 + 7;
const int maxm = 1e6 + 7;
const LL inf = 1e16;

int v[maxm], head[maxn], next[maxm], tot;
LL dist[maxn], w[maxm];
int mp[maxn << 2], M = 1;
int node[10007][17], cnt;
int n, K, m;

inline void ae(int x, int y, LL z) {
    v[++tot] = y; w[tot] = z; next[tot] = head[x]; head[x] = tot;
}

inline int cmp(int a, int b) { return dist[a] < dist[b] ? a : b; }
inline void upd(int x) { mp[x] = cmp(mp[x << 1], mp[x << 1 | 1]); }
inline void build(int n) { while (M < n + 2) M <<= 1; mp[0] = n + 1; }
inline void del(int x) { for (mp[x += M] = 0, x >>= 1; x; upd(x), x >>= 1); }
inline void modify(int x, LL nv) { for (int i = x + M; dist[mp[i]] > nv; mp[i] = x, i >>= 1); dist[x] = nv; }
inline void dijkstra(int n, int s) {
    for (int i = 0; i <= n; ++i) dist[i] = inf;
    build(n); modify(s, 0);
    for (int t = 1; t < n; ++t) {
        int x = mp[1]; del(x);
        for (int i = head[x]; i; i = next[i])
            if (dist[v[i]] > dist[x] + w[i])
                modify(v[i], dist[x] + w[i]);
    }
}

int main() {
    read(n, m, K);
    for (int i = 1; i <= n; ++i)
        for (int j = 0; j <= K; ++j)
            node[i][j] = ++cnt;
    for (int i = 1; i <= m; ++i) {
        int x, y; LL z;
        read(x, y, z);
        for (int j = K; j; --j) ae(node[x][j], node[y][j - 1], z / 2);
        for (int j = 0; j <= K; ++j) ae(node[x][j], node[y][j], z);
    }
    dijkstra(cnt, node[1][K]);
    LL ans = inf;
    for (int i = 0; i <= K; ++i) chkmin(ans, dist[node[n][i]]);
    if (ans < inf) writeln(ans);
    else puts("-1");
    return 0;
}
E DongDong数颜色

题意:给一棵树,树上的每个节点都有一个颜色 c i c_i ci m m m次询问,每次询问以某个点为根的子树中有多少种不同的颜色。

题解:简单来说就是序列上的数颜色(HH的项链)+dfs序就好了。

首先你要会序列上的数颜色:询问一段区间 [ l , r ] [l,r] [l,r]中有多少种不同的颜色。我们设 l a s t ( i ) last(i) last(i)表示 c i c_i ci这个颜色上一次出现的位置,即 l a s t ( i ) last(i) last(i)是最大的 j j j,满足 c j = c i c_j=c_i cj=ci j < i j<i j<i。如果不存在这个位置那么 l a s t ( i ) = 0 last(i)=0 last(i)=0。那么求一段区间 [ l , r ] [l,r] [l,r]中有多少种不同的颜色,我们只要求这段区间中有多少个位置的 l a s t last last值小于 l l l。因为如果 l a s t ( i ) ≥ l last(i)\geq l last(i)l,说明它的颜色在这段区间中不是第一次出现,不需要统计。于是只要离线询问+树状数组,或者主席树在线回答询问,或者莫队都可以搞定(莫队可能略慢)。

然后如果要问一个子树中的情况,我们求出这棵树的dfs序,那么一棵子树在dfs序上就对应一个区间,就转化为了区间数颜色问题。

我写的是主席树。

#include 
#include 
#include 
#include 

template <typename T> inline void read(T& x) {
    int f = 0, c = getchar(); x = 0;
    while (!isdigit(c)) f |= c == '-', c = getchar();
    while (isdigit(c)) x = x * 10 + c - 48, c = getchar();
    if (f) x = -x;
}
template <typename T, typename... Args>
inline void read(T& x, Args&... args) {
    read(x); read(args...); 
}
template <typename T> void write(T x) {
    if (x < 0) x = -x, putchar('-');
    if (x > 9) write(x / 10);
    putchar(x % 10 + 48);
}
template <typename T> inline void writeln(T x) { write(x); puts(""); }
template <typename T> inline bool chkmin(T& x, const T& y) { return y < x ? (x = y, true) : false; }
template <typename T> inline bool chkmax(T& x, const T& y) { return x < y ? (x = y, true) : false; }

const int maxn = 1e5 + 207;

int v[maxn << 1], head[maxn], next[maxn << 1], etot;
int st[maxn], ed[maxn], dfn[maxn], cnt;
int pre[maxn], last[maxn];
int col[maxn], n, m;

struct Node {
    int lc, rc, sum;
    Node() : lc(0), rc(0), sum(0) {}
};
Node T[maxn << 5];
int root[maxn], tot;

inline void ae(int x, int y) {
    v[++etot] = y; next[etot] = head[x]; head[x] = etot;
    v[++etot] = x; next[etot] = head[y]; head[y] = etot;
}
void dfs(int x, int fa) {
    st[x] = ++cnt; dfn[cnt] = x;
    for (int i = head[x]; i; i = next[i])
        if (v[i] != fa) dfs(v[i], x);
    ed[x] = cnt;
}

void insert(int &o, int l, int r, int pos) {
    T[++tot] = T[o]; o = tot; ++T[o].sum;
    if (l == r) return;
    int mid = (l + r) >> 1;
    if (pos <= mid) insert(T[o].lc, l, mid, pos);
    else insert(T[o].rc, mid + 1, r, pos);
}
int query(int lt, int rt, int lb, int rb, int l, int r) {
    if (l > rb || r < lb) return 0;
    if (l <= lb && r >= rb) return T[rt].sum - T[lt].sum;
    int mid = (lb + rb) >> 1;
    return query(T[lt].lc, T[rt].lc, lb, mid, l, r) + query(T[lt].rc, T[rt].rc, mid + 1, rb, l, r);
}

int main() {
    read(n, m);
    for (int i = 1; i <= n; ++i) read(col[i]);
    for (int i = 1, x, y; i < n; ++i) read(x, y), ae(x, y);
    dfs(1, 0);
    for (int i = 1; i <= n; ++i) {
        last[i] = pre[col[dfn[i]]];
        pre[col[dfn[i]]] = i;
        insert(root[i] = root[i - 1], 0, n, last[i]);
    }
    while (m--) {
        int x; read(x);
        writeln(query(root[st[x] - 1], root[ed[x]], 0, n, 0, st[x] - 1));
    }
    return 0;
}
F DongDong的生成树

题意: n n n个结点,一开始没有边, m m m次操作,询问当前图的最小生成树权值和,或者连一条边。如果图未连通输出-1。

题解:LCT裸题。将边化为一个新的结点,边权就是它的点权,LCT维护点权最大值。每次连边时首先看看两个端点是否已经连通,没连通就连,连通的话找出这条路径上边权最大的那条边,看看是否能用新的边取代使得权值变小。

如果会LCT的话这题没有任何技术含量。

#include 
#include 
#include 
#include 

template <typename T> inline void read(T& x) {
    int f = 0, c = getchar(); x = 0;
    while (!isdigit(c)) f |= c == '-', c = getchar();
    while (isdigit(c)) x = x * 10 + c - 48, c = getchar();
    if (f) x = -x;
}
template <typename T, typename... Args>
inline void read(T& x, Args&... args) {
    read(x); read(args...); 
}
template <typename T> void write(T x) {
    if (x < 0) x = -x, putchar('-');
    if (x > 9) write(x / 10);
    putchar(x % 10 + 48);
}
template <typename T> inline void writeln(T x) { write(x); puts(""); }
template <typename T> inline bool chkmin(T& x, const T& y) { return y < x ? (x = y, true) : false; }
template <typename T> inline bool chkmax(T& x, const T& y) { return x < y ? (x = y, true) : false; }

typedef long long LL;

const int maxn = 1e4 + 7, maxm = 1e5 + 7, maxsize = maxn + maxm;
const LL inf = 1e16;

int fa[maxsize], ch[maxsize][2], mp[maxsize];
LL val[maxsize];
bool rev[maxsize];

inline int iden(int x) { return ch[fa[x]][0] == x ? 0 : (ch[fa[x]][1] == x ? 1 : -1); }
inline void update(int x) {
    mp[x] = x;
    if (ch[x][0] && val[mp[ch[x][0]]] > val[mp[x]]) mp[x] = mp[ch[x][0]];
    if (ch[x][1] && val[mp[ch[x][1]]] > val[mp[x]]) mp[x] = mp[ch[x][1]];
}
inline void pushdown(int x) {
    if (rev[x]) {
        rev[ch[x][0]] ^= 1; rev[ch[x][1]] ^= 1;
        rev[x] = 0; std::swap(ch[x][0], ch[x][1]);
    }
}
inline void rotate(int x) {
    int d = iden(x), y = fa[x];
    if (~iden(y)) ch[fa[y]][iden(y)] = x;
    fa[x] = fa[y];
    if ((ch[y][d] = ch[x][d ^ 1])) fa[ch[x][d ^ 1]] = y;
    fa[ch[x][d ^ 1] = y] = x;
    update(y); update(x);
}
int stk[maxsize];
inline void splay(int x) {
    int tp = 1; stk[1] = x;
    for (int i = x; ~iden(i); i = fa[i]) stk[++tp] = fa[i];
    while (tp) pushdown(stk[tp--]);
    while (~iden(x)) {
        int y = fa[x];
        if (~iden(y)) rotate(iden(y) ^ iden(x) ? x : y);
        rotate(x);
    }
}
inline void access(int x) {
    for (int y = 0; x; x = fa[y = x])
        splay(x), ch[x][1] = y, update(x);
}
inline void makeroot(int x) {
    access(x); splay(x); rev[x] ^= 1;
}
inline void link(int x, int y) {
    makeroot(x); fa[x] = y;
}
inline void cut(int x, int y) {
    makeroot(x); access(y); splay(y);
    fa[x] = ch[y][0] = 0;
    update(y);
}

struct Edge {
    int x, y;
    LL w;
    Edge(int a, int b, LL c) : x(a), y(b), w(c) {}
    Edge() : x(0), y(0), w(0) {}
};
Edge es[maxm];
int n, m;

int pa[maxn];
int findf(int x) { return x == pa[x] ? x : pa[x] = findf(pa[x]); }

int main() {
    read(n, m);
    for (int i = 1; i <= n; ++i) val[i] = -inf;
    for (int i = 1; i <= n + m; ++i) mp[i] = i;
    LL ans = 0;
    int cnt = 0;
    for (int i = 1; i <= n; ++i) pa[i] = i;
    for (int i = 1; i <= m; ++i) {
        int q = getchar(); while (isspace(q)) q = getchar();
        if (q == 'Q') writeln(cnt == n - 1 ? ans : -1);
        else {
            int x, y;
            LL z; read(x, y, z);
            es[i] = Edge(x, y, z);
            val[i + n] = z;
            if (findf(x) == findf(y))  {
                makeroot(x); access(y); splay(y);
                int d = mp[y];
                if (es[d - n].w > z) {
                    cut(es[d - n].x, d); cut(d, es[d - n].y);
                    ans -= es[d - n].w;
                    link(x, i + n); link(i + n, y);
                    ans += z;
                }
            } else {
                pa[findf(x)] = pa[findf(y)];
                link(x, i + n);
                link(i + n, y);
                ++cnt; ans += z;
            }
        }
    }
    return 0;
}

总的来说这场比赛的题目质量是相当差的。都是些模板题,就只能给我这种垃圾退役选手当回忆赛打。

你可能感兴趣的:(比赛题解)