Contest Info
Solved | A | B | C | D | E | F |
---|---|---|---|---|---|---|
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)$,那么所有直接输出整个矩阵的和就可以了;没有的话就进行二进制枚举,我们可以考虑枚举选择了多少列,再计算每一行中剩余元素的和进行排序
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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); }
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}$
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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"); }
F.XOR TREE
题意:
给定有$n$个节点的树,已知每个节点的权值,完成两种操作:
- $(u, x)$ 将点$u$ 的权值修改为$x $
- $(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$为偶数的位置提供答案,按照树上,那么就隔一个顶点贡献一次
考虑到次序这个东西可以用深度相邻黑白染色来实现,我们把树拆成两份,黑色一份,白色一份
黑色树上只记录黑色点的权值,白色点权值全部为零,白色树反之
最后用树链剖分+线段树实现就好了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }