Educational Codeforces Round 138 (Rated for Div. 2)练习笔记
\(\text{A. Cowardly Rooks}\)
有一张 \(n\times n(n\leq 8)\) 的国际象棋棋盘,上面放了 \(m(m\leq 8)\) 个城堡(能攻击在同一直线的棋子),第 \(i\) 个城堡位于 \((x_i,y_i)\)。初始时,每个格子只有最多只有一个城堡,且没有任何两个城堡可以攻击到对方。问是否能够移动某个城堡到棋盘某个地方,使得移动后棋盘仍然满足上述的两个条件?多组数据。
只要初始时有一个空行,假设是第 \(i\) 行,那么在移动任意 \((x,y)\) 上的城堡时就可以移动到 \((i,y)\) 上去,所以只要初始时存在空行就存在移动的方案;同理可得,只要初始时有空列,就也存在移动方案。
cint maxn = 11;
int T, n, m, x[maxn], y[maxn], c1[maxn], c2[maxn], t1, t2;
int main(){
T = rd();
while(T--){
n = rd(), m = rd(), t1 = t2 = 0;
fp(i, 1, n)c1[i] = c2[i] = 0;
fp(i, 1, m)x[i] = rd(), y[i] = rd(), c1[x[i]] = c2[y[i]] = 1;
fp(i, 1, n)t1 += !c1[i], t2 += !c2[i];
if(t1 || t2)puts("YES");else puts("NO");
}
return 0;
}
\(\text{B. Death's Blessing}\)
有 \(n(n\leq 2\times 10^5)\) 个小怪排成一排,第 \(i\) 个小怪有 \(a_i(1\leq a_i\leq 10^9)\) 的血量和 \(b_i(0\leq b_i\leq 10^9)\) 的能力值。杀死一只小怪需要耗费等于其血量的时间,同时一个小怪死亡后,它左右的小怪(如果有的话)血量会加上它的能力值,同时它会从这一排中被删去。求最少需要多少时间来杀掉所有的小怪。多组数据。
答案肯定是等于所有怪的血量之和加上打死小怪给旁边怪加的血量之和的。考虑如何最小化后边这个。首先,一个怪在两端被打掉肯定是优于在中间被打掉的,所以我们每次都打两端的某个怪;其次,最后剩下的那个怪的能力值是不会算进答案里的,所以让能力值最大的怪最后被打即可。
cint maxn = 200010;
int T, n, b[maxn], mx;
LL a[maxn], ans;
int main(){
T = rd();
while(T--){
n = rd(), ans = mx = 0;
fp(i, 1, n)a[i] = rd();
fp(i, 1, n)b[i] = rd(), mx = b[i] > b[mx] ? i : mx;
fp(i, 1, mx-1)ans += a[i], a[i+1] += b[i];
fb(i, n, mx+1)ans += a[i], a[i-1] += b[i];
printf("%lld\n", ans+a[mx]);
}
return 0;
}
\(\text{C. Number Game}\)
有 \(n(n\leq 100)\) 张纸牌,第 \(i\) 张纸牌的点数为 \(a_i\),然后 \(\text{Alice}\) 和 \(Bob\) 进行博弈。首先 \(A\) 选一个非负整数 \(k\),然后进行 \(k\) 次游戏。第 \(i\) 次游戏 \(A\) 拿走一张小于等于 \(k-i+1\) 的纸牌,然后如果还有剩余的纸牌, \(B\) 则需要将剩余的某张纸牌的点数加上 \(k-i+1\) 。如果某次 \(A\) 无法拿走任何纸牌,则 \(B\) 获胜;否则 \(A\) 获胜。求在双方都绝顶聪明的情况下,\(A\) 最大能选择多大的 \(k\) 。\(t(t\leq 100)\) 组数据。
首先,\(A\) 肯定会选择能选择的最大的纸牌;其次,由于最后 \(A\) 需要拿走的排点数必定是 \(1\),所以 \(B\) 只需要每次都将一张点数为 \(1\) 的牌加点数即可。那么将纸牌从大到小排序,二分答案,如果最后剩余的 \(1\) 大于等于 \(k\),那么 \(A\) 获胜,否则 \(B\) 获胜。
cint maxn = 110;
int T, n, a[maxn];
int main(){
T = rd();
while(T--){
n = rd();
fp(i, 1, n)a[i] = rd();
sort(a+1, a+1+n), reverse(a+1, a+1+n);
int l = 0, r = n, md, now, ans = 0;
while(l <= r){
md = l+r>>1, now = 1;]
for(int i = 1; i <= md; ++i){
while(now <= n && a[now] > md-i+1)++now;
++now;
if(now > n)break;
}
if(now <= n-md+2)ans = md, l = md+1;
else r = md-1;
}
printf("%d\n", ans);
}
return 0;
}
\(\text{D. Counting Arrays}\)
对于一个长度为 \(n\) 的数列 \(a\),定义它的消除序列为长度为 \(n\) 的数列 \(b\),满足按照以下的步骤可以将 \(a\) 消除:
第 \(i\) 次操作消除 \(a_{b_i}\),\(a_{b_i}\) 需满足 \(\text{gcd}(a_{b_i},b_i)=1\),然后将 \(a_{b_i}\) 之后的数往前补,所以 \(n_i\leq n-i+1\)。
定义一个数列是有歧义的,当且仅当它存在两个及以上的消除序列。求所有长度为 \(1-n(n\leq 3\times 10^5)\) 的,每一位不超过 \(m(m\leq 10^{12})\) 的有歧义的数列个数,对 \(998244353\) 取模。
可以转化为总个数减去没有歧义数列的个数。首先,\(1,1,...,1\) 肯定是所有数列的消除序列。假设第 \(i\) 个数与 \(j(1<j\leq i)\) 互质,那么就可以构造这样一个消除序列:\(1,1,...(i-j个1),j,1,1,...\) 。那么这样一个数列就是有歧义的。所以对于任意的 \(a_i\),都需要满足 \(\text{gcd}(a_i,j)\neq 1\),也就是 \(a_i\) 需要能够被所有 \([1,i]\) 中的质数整除,所以 \(a_i\) 的方案数就等于 \(\frac{m}{\prod_{j=1}^k p_j}(p_{k+1}>i)\)。
cint maxn = 300010, mod = 998244353;
int n, pri[maxn], cnt, isnp[maxn], ans, ans2;
LL m;
int main(){
scanf("%d %lld", &n, &m);
fp(i, 2, n){
if(!isnp[i])pri[++cnt] = i;
for(rg int j = 1; j <= cnt && i*pri[j] <= n; ++j){
isnp[i*pri[j]] = 1;
if(i%pri[j] == 0)break;
}
}
rg int mul = 1;
fp(i, 1, n)mul = m%mod*mul%mod, ans2 = (ans2+mul)%mod;
rg int now = 0, res = 1;
for(rg int i = 1; i <= n; ++i){
while(now < cnt && pri[now+1] <= i)m /= pri[++now];
if(!m)break;
res = m%mod*res%mod, ans = (ans+res)%mod;
}
printf("%d\n", (ans2-ans+mod)%mod);
return 0;
}
\(\text{E. Cactus Wall}\)
在我的世界中,有一块 \(n\times m(n,m\leq 2\times 10^5,nm\leq 4\times 10^5)\) 的沙地,上面初始时存在一些仙人掌。问最少还需要放置多少个仙人掌,可以使得无法从第一排走到最后一排。多组数据。
首先将初始的仙人掌的四周标记为不可用的位置。然后有个错误的思路:设 \(dp_{i.j}\) 表示从第一列的某一个位置开始,拓展到 \((i,j)\) 最少需要多少个仙人掌,每次拓展只能往右上或者右下拓展。但是 \(\text{dp}\) 无法处理这种情况:
\(\text{dp}\) 的答案是 \(2\) ,但实际只需要 \(1\) 个仙人掌。原因就是可能往左上或者左下拓展能够更优。所以把 \(\text{dp}\) 换成最短路即可。用 \(\text{01bfs}\) 实现,复杂度为 \(\text O(nm)\) 。
cint maxn = 200010, inf = 0x3f3f3f3f, dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
cint dir2[4][2] = {{-1, -1}, {-1, 1}, {1, -1}, {1, 1}};
int T, n, m, ans, pos;
int q[maxn*2][2], hd = 1, tl;
string g[maxn];
vector<int> c[maxn], dp[maxn], pre[2][maxn];
il bool chk(cint &x, cint &y){ return 1 <= x && x <= n && 1 <= y && y <= m; }
int main(){
T = rd();
while(T--){
n = rd(), m = rd(), ans = inf;
fp(i, 1, n)cin >> g[i], c[i].resize(m+1), dp[i].resize(m+1), pre[0][i].resize(m+1), pre[1][i].resize(m+1);
fp(i, 1, n)fp(j, 1, m)c[i][j] = 0;
fp(i, 1, n)fp(j, 1, m)if(c[i][j] != inf){
if(g[i][j-1] == '#'){
fp(k, 0, 3){
rg int x = i+dir[k][0], y = j+dir[k][1];
if(chk(x, y))c[x][y] = inf;
}
c[i][j] = 0;
}else c[i][j] = 1;
}
fp(i, 1, n)if(c[i][1] != inf)dp[i][1] = c[i][1], q[++tl][0] = i, q[tl][1] = 1;
fp(i, 1, n)fp(j, 2, m)dp[i][j] = inf;
while(hd <= tl){
rg int x = q[hd][0], y = q[hd++][1];
fp(i, 0, 3){
rg int nx = x+dir2[i][0], ny = y+dir2[i][1];
if(!chk(nx, ny))continue;
if(c[nx][ny] == inf)continue;
if(dp[nx][ny] > dp[x][y]+c[nx][ny]){
dp[nx][ny] = dp[x][y]+c[nx][ny], pre[0][nx][ny] = x, pre[1][nx][ny] = y;
if(!c[nx][ny])q[--hd][0] = nx, q[hd][1] = ny;
else q[++tl][0] = nx, q[tl][1] = ny;
}
}
}
fp(i, 1, n)if(dp[i][m] < ans)ans = dp[i][m], pos = i;
if(ans == inf){ puts("NO"); continue; }
puts("YES");
rg int x = pos, y = m;
while(x){
g[x][y-1] = '#';
rg int nx = pre[0][x][y], ny = pre[1][x][y];
x = nx, y = ny;
}
fp(i, 1, n)cout << g[i] << endl;
}
return 0;
}
\(\text{F. Distance to the Path}\)
给定一棵大小为 \(n(n\leq 2\times 10^5)\) 的树,初始所有点的点权为 \(0\)。接下来有 \(m(m\leq 2\times 10^5)\) 次操作,操作分两种:第一种操作为询问第 \(v\) 个节点的点权。第二种操作为,指定一条路径 \(u\rightarrow v\),将所有到这条路径的距离小于等于 \(d(0\leq d\leq 20)\) 的点的点权加上 \(k(1\leq k\leq 1000)\)。
神奇的做法……将所有操作都转化为给子树内距离小于等于 \(d\) 的点加上 \(k\),这样就不需要真的一个个加,只需要在子树的根打上一个标记,询问时不断跳父亲统计即可。设 \(f_{u,i}\) 表示 \(u\) 的子树内与 \(u\) 距离等于 \(d\) 的点都要加上多少,设 \(fa_u\) 表示 \(u\) 的父亲。那么对于 \(u\) 子树内与 \(u\) 距离为 \(d-1\) 的点,可以转化为与 \(fa_u\) 距离为 \(d\) 的点;与 \(u\) 距离为 \(d-2\) 的点,可以转化为与 \(fa_{fa_u}\) 距离为 \(d\) 的点。所以设 \(\text{LCA}(u,v)\) 为 \(l\),我们可以先将 \([u,l)\) 和 \([v,l)\) (表示这条路径)所有点的 \(f_d\) 都加上 \(k\) ,然后剩下的就是与 \(l\) 距离为 \(d,d-1,...,0\) 的点,与 \(fa_l\) 距离为 \(d-1,d-2,...,0\) 的点,与 \(fa_{fa_l}\) 距离为 \(d-2,d-3,...,0\) 的点……而与 \(l\) 距离为 \(d-2\) 的点可以转化为与 \(fa_l\) 距离为 \(d-1\) 的点,距离为 \(d-3\) 的点可以转化为与 \(fa_l\) 距离为 \(d-2\) 的点……
所以最后就转化成与 \(l\) 距离为 \(d,d-1\) 的点,与 \(fa_l\) 距离为 \(d-1,d-2\) 的点,与 \(fa_{fa_l}\) 距离为 \(d-2,d-3\) 的点……所以需要修改的次数不超过 \(2d\)。给一条路径都加上某个值可以用树剖+树状数组。
cint maxn = 200010, maxw = 21;
int n, m;
int dep[maxn], fa[maxn], size[maxn], son[maxn], dfn[maxn], tot, top[maxn];
int bit[maxw][maxn];
struct edge{ int to, nxt; }e[maxn<<1];
int head[maxn], k;
il void add(cint &u, cint &v){ e[++k] = (edge){ v, head[u] }, head[u] = k; }
void dfs(int u){
dep[u] = dep[fa[u]]+1, size[u] = 1;
go(u)if(!dep[e[i].to]){
fa[e[i].to] = u, dfs(e[i].to), size[u] += size[e[i].to];
if(size[e[i].to] > size[son[u]])son[u] = e[i].to;
}
}
void dfs2(int u, int tp){
dfn[u] = ++tot, top[u] = tp;
if(son[u])dfs2(son[u], tp);
go(u)if(!dfn[e[i].to])dfs2(e[i].to, e[i].to);
}
il void mdf(int d, int x, int v){ for(; x <= n; x += x&-x)bit[d][x] += v; }
il int qry(int d, int x, int ans = 0){
for(; x; x -= x&-x)ans += bit[d][x];
return ans;
}
int main(){
n = rd();
fp(i, 2, n){
rg int u = rd(), v = rd();
add(u, v), add(v, u);
}
dfs(1), dfs2(1, 1);
m = rd();
while(m--){
rg int op = rd();
if(op == 1){
rg int v = rd(), ans = 0;
fp(i, 0, 20){
if(!v)break;
ans += qry(i, dfn[v]), v = fa[v];
}
printf("%d\n", ans);
}else{
rg int u = rd(), v = rd(), k = rd(), d = rd();
while(top[u] != top[v]){
if(dep[top[u]] > dep[top[v]])u ^= v ^= u ^= v;
mdf(d, dfn[top[v]], k), mdf(d, dfn[v]+1, -k), v = fa[top[v]];
}
if(dep[u] > dep[v])u ^= v ^= u ^= v;
mdf(d, dfn[u], k), mdf(d, dfn[v]+1, -k);
if(!d)continue;
if(fa[u])mdf(d-1, dfn[u], k), mdf(d-1, dfn[u]+1, -k);
fp(i, 1, d){
if(fa[u])u = fa[u];
mdf(d-i, dfn[u], k), mdf(d-i, dfn[u]+1, -k);
if(fa[u] && i < d)mdf(d-i-1, dfn[u], k), mdf(d-i-1, dfn[u]+1, -k);
}
}
}
return 0;
}