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;
}
posted @ 2022-04-03 16:33  ╰⋛⋋⊱๑落叶๑⊰⋌⋚╯  阅读(132)  评论(0编辑  收藏  举报