牛客练习赛58

Contest Info


Practice Link

SolvedABCDEF
4/6 O O Ø Ø  -  Ø
  • O 在比赛中通过 
  • Ø 赛后通过
  • ! 尝试了但是失败了
  • - 没有尝试

Solutions


C.矩阵消除游戏

题意:

有一个$n*m$的矩阵,牛妹可以进行$k$次操作,每次任选一行/列将这列所有元素变为0,并将他们的和纳入自己的得分,要求最大化得分

思路:

最开始是想到了贪心,样例能过但是$WA$了,这里给出个反例:

3  3  2
100 100 1
5   10  2
10  5   5

后来觉得数据这么小,应该可以枚举,然而一时不知道怎么枚举,赛后补题发现就是自己原来看过的二进制枚举

分析可以发现假如$kgeqslant min(n, m)$,那么所有直接输出整个矩阵的和就可以了;没有的话就进行二进制枚举,我们可以考虑枚举选择了多少列,再计算每一行中剩余元素的和进行排序

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n, m, k, ans, tot;
int a[20][20], sum[20];
bool cmp(int x, int y){
    return x > y;
}
int main(){
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++) 
            scanf("%d", &a[i][j]),tot += a[i][j];
    if(k>=min(n,m)) return !printf("%d", tot);
        
    for(int i = 0; i < 1<<m; i++){
        int tmp = i, cnt = 0;
        while(tmp) cnt++, tmp &= (tmp-1);
        if(cnt>k) continue;
        for(int i = 1; i <= n; i++) sum[i] = 0;
        int num = i, res = 0;
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                if(num&(1<<(j-1))) res += a[i][j];
                else sum[i] += a[i][j]; 
            }
        }
        sort(sum+1, sum+1+n, cmp);
        for(int i = 1; i <= k-cnt; i++) res += sum[i];
        ans = max(ans, res);
    }
    return !printf("%d", ans);
} 
View Code

D.迷宫

题意:

有一个$n∗m$的迷宫,迷宫中存在些障碍,牛妹优先按照右、下、左、上的顺序移动,问是否可以添加一些障碍使得牛妹能从格子$(1,1)$走到格子$(n,m)$,如果有则输出最小值,没有输出$-1$

思路:

这题我拿到一开始的想法是$dfs$去搜索各个状态,但是莫名其妙样例都没过,写出来也有可能爆了

其实就是经典的棋盘$dp$问题,只不过有小小的思维量

认真思考下你会发现,她不可能向左和向上走:

  • 不能向左走:向左走后,所处位置右边的格子会空出来,它又会走回去,在两者间来回跳跃
  • 不能向上走:向上走,说明右、下、左三个格子都是障碍,它只能是由上面走下来的。这说明他上面那个格子不能向右走,它走到上面格子后,只能选择向下,然后上下来回跳跃

这样问题问题转换为:有一个$n∗m$的迷宫,牛妹只能向右和向下走,但优先向右走,是否可以添加一些障碍使得牛妹能从格子$(1,1)$走到格子$(n,m)$

我们定义$dp[i][j]$为到达$(i,j)$所需要的最小改变数,则有:

$dp_{i,j}->dp_{i,j+1}$

$dp_{i,j}+s[i][j+1]=='0')->dp_{i+1,j}$

#include <cstdio>
#include <cstring>
#include <algorithm>
#define inf 0x3f3f3f3f
using namespace std;
int n, m, dp[1010][1010];
char s[1010][1010];
int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%s", s[i]+1);
    memset(dp, 0x3f, sizeof(dp));
    dp[1][1] = 0;
    for(int i = 1;i <= n; i++){
        for(int j = 1;j <= m; j++){
            if(s[i][j]!='0') continue;
            if(s[i][j+1]=='0'){
                dp[i][j+1] = min(dp[i][j+1], dp[i][j]);
                if(s[i+1][j]=='0') dp[i+1][j] = min(dp[i+1][j], dp[i][j] + 1);
            }
            else if(s[i+1][j]=='0') dp[i+1][j] = min(dp[i+1][j], dp[i][j]);
        }
    }
    if(dp[n][m]!=inf) printf("%d", dp[n][m]);
    else printf("-1");
} 
View Code

F.XOR TREE

题意:

给定有$n$个节点的树,已知每个节点的权值,完成两种操作:

  1. $(u, x)$ 将点$u$ 的权值修改为$x $
  2. $(u, v)$求点$u $到点$v $之间的所有子路径的长度的异或和(路径长度定义为两点间最短路径上的所有结点的权值的异或和)

思路:

这题重点在于如何根据题目给的条件去找到规律方法简化这个问题,而异或一个重要的性质就是异或两次就会相消

一条长为$k$的路径$v_{1}, v_{2},  dots , v_{k}$,对其中一个点$v_{i}$,我们考虑它的贡献,即在所有子区间中出现的次数,由排列组合可得为$i*(k-i+1)-1$(一个点本身不算路径)

根据异或的特性,所有出现次数为奇数的点可并入答案中,分析$i*(k+i-1)-1=i*k-i*(i+1)-1$得:

  • 当$k$为偶数,所有位置的次数都是奇数,那么在树上查询就可以了。
  • 当$k$为奇数,那么就是$i$为偶数的位置提供答案,按照树上,那么就隔一个顶点贡献一次

考虑到次序这个东西可以用深度相邻黑白染色来实现,我们把树拆成两份,黑色一份,白色一份

黑色树上只记录黑色点的权值,白色点权值全部为零,白色树反之

最后用树链剖分+线段树实现就好了

#include <cstdio>
#include <cstring>
#include <algorithm>
#define lson rt<<1, l , mid
#define rson rt<<1|1, mid+1 , r
using namespace std;
const int maxn = 2e5+100;
int n, q, u, v, op, x, y, a[maxn];
int cnt, head[maxn];
int tot, dep[maxn], siz[maxn], id[maxn], f[maxn], son[maxn], top[maxn];
int sum[2][maxn<<2];
struct edge{
    int to, nxt;
}e[maxn<<1];
void add(int u, int v){
    e[++cnt].nxt = head[u], e[cnt].to = v;
    head[u] = cnt; 
}
void dfs1(int u, int fa){
    siz[u] = 1;
    for(int i = head[u]; i; i = e[i].nxt){
        int v = e[i].to;
        if(v==fa) continue;
        dep[v] = dep[u] + 1, f[v] = u;
        dfs1(v, u), siz[u] += siz[v];
        if(siz[v]>siz[son[u]]) son[u] =  v;
    }
}
void dfs2(int u, int topf){
    top[u] = topf, id[u] = ++tot;
    if(son[u]) dfs2(son[u], topf);
    for(int i = head[u]; i; i = e[i].nxt){
        int v = e[i].to;
        if(v==son[u]||v==f[u]) continue;
        dfs2(v, v);
    }
}
void push_up(int x, int rt){
    sum[x][rt] = sum[x][rt<<1]^sum[x][rt<<1|1];
}
void update(int x, int rt, int l, int r, int pos, int val){
    if(l==r){
        sum[x][rt] = val;
        return;
    }
    int mid = (l+r)>>1;
    if(pos<=mid) update(x, lson, pos, val);
    else update(x, rson, pos, val);
    push_up(x, rt);
}
int query(int x, int rt, int l, int r, int ql, int qr){
    if(ql<=l&&r<=qr) return sum[x][rt];
    int mid = (l+r)>>1;
    int res = 0;
    if(ql<=mid) res ^= query(x, lson, ql, qr);
    if(qr>mid) res ^= query(x, rson, ql, qr);
    return res;
}
int getsum(int x, int u, int v){
    int ans = 0;
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]]) swap(u, v);
        ans ^= query(x, 1, 1, n, id[top[u]], id[u]);
        u = f[top[u]];
    }
    if(dep[u]>dep[v]) swap(u, v);
    ans ^= query(x, 1, 1, n, id[u], id[v]);
    return ans;
}
int main(){
    scanf("%d%d", &n, &q);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for(int i = 1; i <= n-1; i++){
        scanf("%d%d", &u, &v);
        add(u, v), add(v, u);
    }
    dfs1(1, 1), dfs2(1, 0);
    for(int i = 1; i <= n; i++) update(dep[i]%2, 1, 1, n, id[i], a[i]);
    while(q--){
        scanf("%d%d%d", &op, &x, &y);
        if(op==1) update(dep[x]%2, 1, 1, n, id[x], y);
        else{
            if(dep[x]%2!=dep[y]%2) printf("%d
", getsum(0, x, y)^getsum(1, x, y));
            else printf("%d
", getsum((dep[x]%2)^1, x, y)); 
        }
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/wizarderror/p/12416219.html