Monoxer Programming contest 2024(AtCoder Beginner contest 345)

寒暄:这次 D 我真的是服了,一直 TLE,加了很多个剪枝变成 TLE*1。做 C 的时候又跳到 E,F 去,E,F 居然没做出来,导致 C 较晚才提交通过。

膜拜:TQAFC 居然过了 F ?

C

如果交换的两个字符是不同的,那么就可以得到一个全新字符串。如果交换的两个字符是相同的,就是原来的串。如果存在两个字符是相同的,那么答案需要加上本身。

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int N = 1e6 + 5;

int n, vis[27], sum[27], res, flg;
char a[N];

signed main() {
	cin >> (a + 1);
	n = strlen(a + 1);
	for (int i = 1; i <= n; i++) vis[a[i] - 'a' + 1]++; // 统计每个字符出现次数
	for (int i = 1; i <= 26; i++) sum[i] = sum[i - 1] + vis[i], flg += (vis[i] >= 2); 
	// flg 代表能不能成为原串
	for (int i = 1; i <= 26; i++) res += vis[i] * (sum[26] - sum[i]); 
	cout << res + (flg > 0) << endl;	
}

复杂度 \(O(n)\)

D

因为每个图块不一定都用,我们可以枚举用哪些图块。按照这样的搜索方式死活过不去,我们可以加一个比较强的剪枝,先搜索面积较大的图块,这样可以让搜索的范围更少!

当然还有个剪枝是:剩下的面积总和不足以填充图块就返回。但是这个让我 TLE *1。

其实有个让多重 for 循环化简的方式:#define _for(i, a, b) for (int i = (a); i <= (b); i++)

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)
const int N = 10 + 5;

int n, h, w, a[N], b[N], tot, c[N][N];

struct edge {
	int x, y;
}ed[N];

bool cmp(edge x, edge y) {
	return x.x * x.y > y.x * y.y;
}

void dfs(int u) {
	if (u == tot + 1) {
		puts("Yes");
		exit(0);
	}
	_for (i, 1, h - ed[u].x + 1) {
		_for(j, 1, w - ed[u].y + 1) {
			int flg = 0;
			_for (a, i, i + ed[u].x - 1) {
				_for (b, j, j + ed[u].y - 1) {
					if (c[a][b]) {
						flg = 1;
						break;
					}
				}
			}
			if (flg) continue;
			_for (a, i, i + ed[u].x - 1) {
				_for (b, j, j + ed[u].y - 1) c[a][b]++;
			}
			dfs(u + 1);
			_for (a, i, i + ed[u].x - 1) {
				_for (b, j, j + ed[u].y - 1) c[a][b]--;
			}
		} 
	}
	swap(ed[u].x, ed[u].y);
	_for (i, 1, h - ed[u].x + 1) {
		_for(j, 1, w - ed[u].y + 1) {
			int flg = 0;
			_for (a, i, i + ed[u].x - 1) {
				_for (b, j, j + ed[u].y - 1) {
					if (c[a][b]) {
						flg = 1;
						break;
					}
				}
			}
			if (flg) continue;
			_for (a, i, i + ed[u].x - 1) {
				_for (b, j, j + ed[u].y - 1) c[a][b]++;
			}
			dfs(u + 1);
			_for (a, i, i + ed[u].x - 1) {
				_for (b, j, j + ed[u].y - 1) c[a][b]--;
			}
		} 
	}
	swap(ed[u].x, ed[u].y);
}

signed main() {
	cin >> n >> h >> w;
	_for (i, 0, n - 1) cin >> a[i] >> b[i];
	_for (i, 1, (1 << n) - 1) {
		memset(c, 0, sizeof c);
		tot = 0; int sum = 0;
		_for(j, 0, n - 1) if (i >> j & 1) ed[++tot] = {a[j], b[j]}, sum += a[j] * b[j];
		if (sum != h * w) continue;
		sort(ed + 1, ed + tot + 1, cmp);
		dfs(1);
	}
	puts("No");
}

复杂度:\(O(?)\)

E

定义 \(dp_{i,j}\) 代表:前 \(i\) 个数删了 \(j\) 个,且第 \(i\) 个保留的最大收益。

为什么这么定义状态?这样就知道保留的那些数的颜色了。

则转移:\(dp_{i,j}=\max_{p<i,C_p≠C_i} \{dp_{p,j-1}\}+V_i\)

状态是 \(O(NK)\) 的,这就要求转移必须 \(O(1)\)。我们定义 \(g_{i,j}=\max_{p<i,C_p≠C_i} \{dp_{p,j-1}\}\),则 \(dp_{i,j}=g_{i,j}+V_i\)。如果不要求颜色不同的话 则有 \(g_{i,j}=\max\{dp_{i-1,j},g_{i-1,j-1}\}\)

对于要求颜色不同的来讲,为了避免颜色冲突,我们把次大值也加入 \(g\) 中,如果最大值的颜色和当前有冲突,则用次大值。

#include <bits/stdc++.h>
using namespace std;

#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)
#define int long long
const int N = 3e5 + 5, M = 505, inf = 0x3f3f3f3f3f3f3f3f;

int n, k, c[N], v[N], dp[M], res = -2e9;

struct edge {
	int mx, mn, id;
	void insert(int x, int p) {
		if (p == id) mx = max(mx, x);
		else {
			if (x > mx) mn = mx, mx = x, id = p;
			else if (x > mn) mn = x;
		}
	}
	int query(int x) {
		if (x == id) return mn;
		return mx;
	}
}g[N];

signed main() {
	cin >> n >> k;
	_for(i, 1, n) cin >> c[i] >> v[i];
	if (n == k) puts("0"), exit(0);
	g[0].mx = 0, g[0].mn = 0, g[0].id = 0; // 初始化为0
	_for(i, 1, n) g[i].mx = g[i].mn = -inf, g[i].id = 0; 
	_for(i, 1, n) {
		memset(dp, -0x3f, sizeof dp);
		_for(j, 0, k) {
			dp[j] = g[j].query(c[i]) + v[i];
			if (k - j == n - i) res = max(res, dp[j]);
		}
		_pfor(j, k, 1) g[j] = g[j - 1];
		g[0].mx = g[0].mn = -inf, g[0].id = 0;
		_for(j, 0, k) g[j].insert(dp[j], c[i]);
	}
	if (res < 0) puts("-1");
	else cout << res << endl;
}

复杂度 \(O(NK)\)

F

假设 \(G\) 是输入信息给出的图。为简单起见,我们首先假设 \(G\) 是连通的。我们有如下事实:

点亮的灯的数量总是偶数。\((\ast)\)

根据这一事实,我们有以下事实:

当且仅当答案为 "是 "时, \(K\) 为偶数。

首先如果 \(K\) 为奇数则无解。

\(X\) 是不大于 \(N\) 的最大偶数。我们首先看如果 \(K = X\),答案是 Yes。

我们可以按照下面的算法,通过最多 \(M\) 次运算得到 \(K = X\)

  • 取一棵以顶点 \(1\) 为根的树 \(T\) ,它是 \(G\) 的子图。
  • 重复下面的操作,直到 \(T\) 只剩下一个顶点。
    • 首先,取 \(T\) 的叶子 \(v\) 。假设 \(e\) 是连接 \(v\) 的边。
    • 如果 \(v\) 未被点亮,则选择 \(e\) 进行操作。
    • 然后,从 \(T\) 中删除顶点 \(v\) 和边 \(e\)

通过这些操作,除了顶点 \(1\) 上的灯之外,其他任何灯都亮。(顶点 \(1\) 上的灯点亮或未点亮是未知的)因此,当算法结束时,将有 \((N-1)\)\(N\) 盏灯点亮。同时, \((\ast)\) 可以保证点亮的灯的数量总是偶数。因此,结束操作后点亮的灯的数量为是 \((N-1)\) 还是 \(N\) 的话,以偶数为准。这个数字与 \(X\) 重合。此外,每条边最多被操作选择一次,因此操作次数是不多于 \(M\) 的。

现在我们考虑偶数 \(K\) 是一个不等于 \(X\) 的情况。在这种情况下, \(0 \leq K \lt X\) 。考虑按照上述算法进行运算。每次操作点亮的灯的数量都会增加两盏或保持不变。则刚好有一个时刻亮灯的数量变为 \(K\)

复杂度为 \(O(N + M)\)

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int N = 3e5 + 5;

int n, m, k, vis[N], sta[N], cnt;
vector<pair<int, int> > G[N]; 
vector<int> ans;

void dfs(int u) {
	vis[u] = 1;
	for (auto e : G[u]) {
		int v = e.first, id = e.second;
		if (vis[v]) continue;
		dfs(v); // 先 dfs,因为我们从叶子节点逐渐往上点亮
		if (sta[v] == 0 && cnt < k) {
			ans.push_back(id);
			cnt -= sta[v] + sta[u];
			sta[u] ^= 1, sta[v] ^= 1;
			cnt += sta[v] + sta[u];
		}
	}
}

signed main() {
	cin >> n >> m >> k;
	for (int i = 1; i <= m; i++) {
		int u, v;
		cin >> u >> v;
		G[u].push_back({v, i});
		G[v].push_back({u, i});
	}
	for (int i = 1; i <= n; i++) if (!vis[i]) dfs(i);
	if (cnt == k) {
		puts("Yes");
		cout << ans.size() << endl;
		for (auto v : ans) cout << v << ' '; 
 	}
 	else puts("No");
}

评价:这次 abc 出的真好。难度:A<B<C<D<F<E。

posted @ 2024-03-18 23:15  Otue  阅读(9)  评论(0编辑  收藏  举报