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{...#.}\\ \text{..#.#}\\ \text{.....}\\ \text{#.#..}\\ \text{.#...} \]

\(\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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章