1. 程式人生 > 其它 >AtCoder Beginner Contest 246[題解D~G]

AtCoder Beginner Contest 246[題解D~G]

\(ABC246\)

\(D\)

題目大意

給定一個數 \(n\),找到一個滿足一下條件的最小整數 \(x\)

  • \(x\)\(n\) 大。

  • 存在一對非負整數 \(a\)\(b\),使得 \(x = a^3 + a^2b + ab^2 + b^3\)

\(0\leq n \leq 10^{18}\)

\(Sol\)

發現無論 \(a\) 或是 \(b\),上界都是 \(10^6\),且顯然,關於 \(x\) 的二元函式在定義域上單調遞增,考慮列舉其中一個數,二分另外一個數,記錄比 \(n\) 大的最小函式值即可。

\(code\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int lim = 1e6, INF = 5e18;
inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}
int n, ans = INF;
inline int get_val(int a, int b) { return (a * a + b * b) * (a + b); } 
signed main()
{
	n = read();
	for(register int i = 0; i <= lim; i++){
		int l = 0, r = lim;
		while(l <= r){
			int mid = (l + r) / 2;
			if(get_val(i, mid) >= n)
				ans = min(ans, get_val(i, mid)), r = mid - 1;
			else l = mid + 1;
		}
	}
	printf("%lld\n", ans);
	return 0;
}

\(E\)

題目大意

給定一個 \(n\times n\) 的矩陣 \(S\),由字元 # 和 . 組成,分別有以下含義:

  • 若 $S_{i,j} = $ # 則 \((i,j)\) 存在障礙。

  • 若 $S_{i,j} = $ . 則 \((i,j)\) 為空。

給定一個起點和一個終點,起點處有一個棋子,移動方式和國際象棋中的象相同,但路徑上不能存在障礙,問從起點到終點至少移動多少次。

\(2\leq n \leq 1500\)

\(Sol\)

一個比較顯然的最短路問題。

記錄一下移動的方向,如果方向與上次移動的方向相同則邊權為 \(1\),否則為 \(0\)。需要注意的是對於每個位置從不同方向更新得到的答案需要分開考慮,否則會出現如下情況:

當前位置值最小,從其它方向更新到當前位置的值與該最小值相同,若不考慮後者,則從當前位置更新時,與後者方向相同的移動方式答案偏大。

大根堆存在 \(log\),直接使用可能會超時,考慮如何去掉這個 \(log\)。發現邊權只存在 \(1\)\(0\),使用雙端佇列儲存狀態,每次取出隊首,每個狀態向下更新時,若邊權為 \(0\) 則放在隊首,否則放在隊尾。

\(code\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int dx[4] = {1, -1, 1, -1}, dy[4] = {1, -1, -1, 1};
const int N = 2e3 + 10, INF = 1e18;
inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}
struct node{ int x, y, opt; };
int n, sx, sy, tx, ty;
int dis[N][N][4];
bool vis[N][N][4];
char s[N][N];
deque<node> q;
inline bool BFS()
{
	while(!q.empty()){
		node pos = q.front(); q.pop_front();
		if(vis[pos.x][pos.y][pos.opt]) continue;
		if(pos.x == tx && pos.y == ty) { printf("%lld\n", dis[tx][ty][pos.opt]); return true; }
		vis[pos.x][pos.y][pos.opt] = true;
		for(register int i = 0; i <= 3; i++){
			int nx = pos.x + dx[i], ny = pos.y + dy[i];
			if(nx < 1 || ny < 1 || nx > n || ny > n) continue;
			if(s[nx][ny] == '#') continue;
			int w = (i == pos.opt) ? 0 : 1;
			if(dis[nx][ny][i] > dis[pos.x][pos.y][pos.opt] + w){
				dis[nx][ny][i] = dis[pos.x][pos.y][pos.opt] + w;
				if(!w) q.push_front((node){nx, ny, i});
				else q.push_back((node){nx, ny, i});
			}
		}
	}
	return false;
}
signed main()
{
	n = read();
	memset(dis, 127, sizeof(dis));
	sx = read(), sy = read(), tx = read(), ty = read();
	if((sx + sy) % 2 != (tx + ty) % 2) { puts("-1"); return 0; }
	for(register int i = 1; i <= n; i++) scanf("%s", s[i] + 1);
	for(register int i = 0; i <= 3; i++) dis[sx][sy][i] = 0;
	for(register int i = 0; i <= 3; i++){
		int nx = sx + dx[i], ny = sy + dy[i];
		if(nx < 1 || ny < 1 || nx > n || ny > n) continue;
		if(s[nx][ny] == '#') continue;
		dis[nx][ny][i] = 1, q.push_back((node){nx, ny, i});
	}
	if(!BFS()) puts("-1");
	return 0;
}

\(F\)

給定 \(n\) 個打字機,每個打字機有不同的按鍵,這些按鍵屬於 \(a\)\(z\) ,但不一定包含完全。

你可以選擇任意一個打字機,用其中的按鍵打出任意長度為 \(l\) 的字串,問有多少個不同的字串。

模數 \(998244353\)

\(1\leq n\leq18,1\leq l\leq 10^9\)

\(Sol\)

考慮容斥,顯然,若一段字母同屬 \(x\) 個不同的打字機,若 \(x\) 為奇數容斥係數為正,反之為負。

關鍵在於如何列舉。

將集合轉化為二進位制,列舉打字機的不同組合,找出這些打字機公共的字元,再直接容斥即可。

\(code\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 30, mod = 998244353;
inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}
int n, l, ans;
int v[N];
inline int power(int x, int k)
{
	int res = 1;
	while(k){
		if(k & 1) res = res * x % mod;
		x = x * x % mod, k >>= 1;
	}
	return res;
}
inline int cnt(int x)
{
	int res = 0;
	while(x){
		if(x & 1) res++;
		x >>= 1;
	}
	return res;
}
signed main()
{
	n = read(), l = read();
	for(register int i = 0; i < n; i++){
		string s; cin >> s;
		for(register int j = 0; j < s.size(); j++)
			v[i] = v[i] | (1 << (s[j] - 'a')); 
	}
	for(register int i = 1; i < (1 << n); i++){
		int ch = (1 << 26) - 1;
		for(register int j = 0; j < n; j++)
			if(i & (1 << j)) ch &= v[j];
		int k = cnt(ch);
		if(cnt(i) % 2) ans = (ans + power(k, l)) % mod;
		else ans = ((ans - power(k, l)) % mod + mod) % mod;
	}
	printf("%lld\n", ans);
	return 0;
}

\(G\)

題目大意

給定一棵根為 \(1\) 的樹,除根外每個節點有一個權值。

\(A\)\(B\) 一起玩一個移動棋子的遊戲:

  • 棋子最開始在根節點。

  • \(A\) 每次可以將棋子移動到某一個當前節點的某個子節點上。

  • \(B\) 可以在 \(A\) 移動前,將某個節點上的權值變成 \(0\)

  • 如果棋子到了葉子結點,遊戲結束,同時,\(A\) 可以在任意節點上終止遊戲。

  • \(A\) 想要棋子最終所在節點的權值儘可能的大,\(B\) 想要這個值儘可能小。

求兩人都是最優決策下,棋子最終所在節點權值的大小。

\(Sol\)

考慮如果棋子已經被 \(A\) 移動到了某個節點,\(B\) 的操作。

顯然,\(B\) 會刪掉當前子樹內的最優答案。

考慮一個從下往上的過程,如果當前節點為 \(u\),當棋子在 \(u\) 的父親 \(f\) 上時,\(B\) 將會多獲得一次更改機會,他一定會把 \(f\) 子樹中最大的答案刪去。

於是從下往上 \(dp\),用一顆線段樹儲存每個節點的值,詢問子樹內的最大值可以用 \(dfn\) 序把原樹轉化為序列。

\(code\)

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}
struct node{ int in, out; }dfn[N];
struct Tree{
	int v, pos;
	bool operator < (const Tree &x)const{
		return v < x.v;
	}
}tr[8 * N];
int n, cnt;
int A[N];
vector<int> T[N];
inline void DFS(int u, int fa)
{
	dfn[u].in = ++cnt;
	for(register int to : T[u])
		if(to != fa) DFS(to, u);
	dfn[u].out = ++cnt;
}
inline void update(int k, int l, int r, int x, int v)
{
	if(r < dfn[x].in || l > dfn[x].in) return;
	if(l == r && l == dfn[x].in){ tr[k].v = v, tr[k].pos = x; return; }
	int mid = (l + r) >> 1;
	update(k << 1, l, mid, x, v), update(k << 1 | 1, mid + 1, r, x, v);
	tr[k] = max(tr[k << 1], tr[k << 1 | 1]); 
}
inline Tree ask(int k, int l, int r, int x, int y)
{
	if(r < x || l > y) return (Tree){-1, -1};
	if(l >= x && r <= y) return tr[k];
	int mid = (l + r) >> 1;
	return max(ask(k << 1, l, mid, x, y), ask(k << 1 | 1, mid + 1, r, x, y));
}
inline void Sol(int u, int fa)
{
	for(register int to : T[u])
		if(to != fa) Sol(to, u);
	int l = dfn[u].in + 1, r = dfn[u].out - 1;
	if(l > r) return;
	Tree res = ask(1, 1, cnt, l, r);
	update(1, 1, cnt, res.pos, 0);
}
signed main()
{
	n = read();
	for(register int i = 2; i <= n; i++) A[i] = read();
	for(register int i = 1; i < n; i++){
		int x = read(), y = read();
		T[x].push_back(y), T[y].push_back(x);
	}
	DFS(1, 0);
	for(register int i = 1; i <= n; i++)
		update(1, 1, cnt, i, A[i]);
	Sol(1, 0);
	printf("%lld\n", ask(1, 1, cnt, 1, cnt).v);
	return 0;
}