AtCoder Beginner Contest 343

右下角可以切换黑白背景。括号内的数为难度评分。

C. 343 (*220)

枚举开根后的数,再来判断回文。\(N\leq 10^{18}\),所以最多枚举 \(10^6\) 个数。

int check(int x) {
	tot = 0;
	while (x) {
		b[++tot] = x % 10;
		x /= 10;
	}
	for (int i = 1; i <= tot; i++) {
		if (b[i] != b[tot - i + 1]) return 0;
	}
	return 1;
}

signed main() {
	cin >> n;
	for (int i = 1; i * i * i <= n; i++) {
		if (check(i * i * i)) res = max(res, i * i *i );
	}
	cout << res;
}

D. Diversity of Scores (*422)

开一个 map 存储每个数出现的次数。最初 \(0\) 出现了 \(n\) 次。再开一个全局变量 \(res\) 表示有多少个不同的数。

map<int, int> vis;

void del(int x) {
	if (vis[x] == 1) res--;
	vis[x]--;
}

void add(int x) {
	if (vis[x] == 0) res++;
	vis[x]++;
}

signed main() {
	cin >> n >> m;
	for (int i = 1; i <= m; i++) cin >> a[i] >> b[i];
	vis[0] = n; res = 1;
	for (int i = 1; i <= m; i++) {
		del(t[a[i]]);
		t[a[i]] += b[i];
		add(t[a[i]]);
		cout << res << endl;
	}
}

F. Second Largest Query(*1370)

线段树做法。可以维护每个区间的最大,次大,最大出现次数,次大出现次数,合并的时候需要一些麻烦操作,不推荐此做法。

分块做法。这道题只求出严格第 \(2\) 大数。可以维护每一个块的最大值 \(mx_i\),次大值 \(mn_i\)。定义 \(cnt_{i,j}\) 表示数 \(i\) 再第 \(j\) 个块的出现次数。

对于修改操作 \(a_x=v\):暴力修改 \(a_x\),再重新更新一遍 \(x\) 所属的块的最大,次大。复杂度 \(O(\sqrt n)\)

void push_up(int x) {
	mx[x] = mi[x] = 0;  // 必须要清空
	for (int j = lx[x]; j <= rx[x]; j++) mx[x] = max(mx[x], a[j]);
	for (int j = lx[x]; j <= rx[x]; j++) {
		if (a[j] != mx[x]) mi[x] = max(mi[x], a[j]);
	}
}

对于查询操作 \([l,r]\):先利用 \(mx_i\) 求出 \([l,r]\) 的最大值,再用这个最大值去寻找次大值。再用次大值去找次大值出现次数。复杂度 \(O(\sqrt n)\)

注意事项:数的值域比较大,可以离散化!

const int N = 4e5 + 5, sq = 600;
// sq 为块长,因为 cnt 数组的空间为 n*n/sq,所以可以稍稍将块长调大。(不过在 atcoder 上不怕炸空间)
int n, q, a[N], b[N], tot;
int op[N], l[N], r[N], cnt[N][420];
int mx[N], mi[N], lx[N], rx[N], bel[N], idx;

int main() {
	cin >> n >> q;
	for (int i = 1; i <= n; i++) cin >> a[i], b[++tot] = a[i];
	for (int i = 1; i <= q; i++) {
		cin >> op[i] >> l[i] >> r[i];
		if (op[i] == 1) b[++tot] = r[i];
	} 
	sort(b + 1, b + tot + 1);
	tot = unique(b + 1, b + tot + 1) - b - 1;
	for (int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + tot + 1, a[i]) - b; 
	for (int i = 1; i <= q; i++) {
		if (op[i] == 1) r[i] = lower_bound(b + 1, b + tot + 1, r[i]) - b;
	}
	for (int i = 1; i <= n / sq; i++) lx[i] = (i - 1) * sq + 1, rx[i] = i * sq;
	idx = n / sq;
	if (n % sq) idx++, lx[idx] = rx[idx - 1] + 1, rx[idx] = n;
	for (int i = 1; i <= idx; i++) {
		for (int j = lx[i]; j <= rx[i]; j++) bel[j] = i;
	}
	for (int i = 1; i <= idx; i++) { // 处理好块内信息
		for (int j = lx[i]; j <= rx[i]; j++) mx[i] = max(mx[i], a[j]), cnt[a[j]][i]++;
		for (int j = lx[i]; j <= rx[i]; j++) {
			if (a[j] != mx[i]) mi[i] = max(mi[i], a[j]);
		}
	}
	for (int cas = 1; cas <= q; cas++) {
		if (op[cas] == 1) {
			cnt[a[l[cas]]][bel[l[cas]]]--;
			a[l[cas]] = r[cas];
			cnt[a[l[cas]]][bel[l[cas]]]++;
			push_up(bel[l[cas]]);
		}
		else {
			int t1 = bel[l[cas]], t2 = bel[r[cas]];
			int maxn = 0, minn = 0, res = 0;
			if (t1 == t2) { // 在同一个块,暴力扫一遍
				for (int i = l[cas]; i <= r[cas]; i++) maxn = max(maxn, a[i]);
				for (int i = l[cas]; i <= r[cas]; i++) {
					if (maxn != a[i]) minn = max(minn, a[i]);
				}
				for (int i = l[cas]; i <= r[cas]; i++) {
					if (minn == a[i]) res++;
				}
				cout << res << endl;
			}
			else {
				for (int i = l[cas]; i <= rx[t1]; i++) maxn = max(maxn, a[i]);
				for (int i = lx[t2]; i <= r[cas]; i++) maxn = max(maxn, a[i]);
				for (int i = t1 + 1; i <= t2 - 1; i++) maxn = max(maxn, mx[i]); // maxn 求出最大值
				for (int i = l[cas]; i <= rx[t1]; i++) {
					if (a[i] != maxn) minn = max(minn, a[i]);
				}
				for (int i = lx[t2]; i <= r[cas]; i++) {
					if (a[i] != maxn) minn = max(minn, a[i]);
				}
				for (int i = t1 + 1; i <= t2 - 1; i++) {
					if (mx[i] != maxn) minn = max(minn, mx[i]);
					else minn = max(minn, mi[i]);	
				} // minn 求出次大值
				for (int i = l[cas]; i <= rx[t1]; i++) if (a[i] == minn) res++;
				for (int i = lx[t2]; i <= r[cas]; i++) if (a[i] == minn) res++;
				for (int i = t1 + 1; i <= t2 - 1; i++) res += cnt[minn][i]; // 统计出现次数
				cout << res << endl;
			}
		}
	}
} 

G. Compress Strings(*2114)

第一次场切 不是数据结构的 G,激动。

找到一个最短串,这个串包含给定的 \(n\) 个串。首先可以发现,如果这 \(n\) 个串之间有相互包含的两个串,那么那个短串就可以忽略。比如样例 snukeukuk 可以完全忽视掉。

去掉包含的情况后,那么这 \(n\) 个串就是相交的情况了(相交的长度可以为 \(0\))。比如样例 snuke kensho,其中 ke 就是相交的串。

那么最短长度就是:把 \(n\) 个串按照任意顺序拼接起来,去掉中间相交的部分后的最短长度。

可以状压 \(dp\)\(dp_{i,j}\) 表示当前已经拼接了 \(i\) 的集合,最后一个串是串 \(j\) 的最短拼接长度。

转移:\(dp_{i|1<<k,k}=\min\{dp_{i,j}+len_k-cost_{j,k}\}\)。即枚举串 \(k\),将其拼接在串 \(j\) 的后面,并且去掉公共部分长度 \(cost_{j,k}\)

先预处理 \(cost\) 数组,不需要 KMP 和 hash,暴力处理就行,原因自己思考一下。总复杂度 \(n^22^n\)

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

const int N = 21, M = 2e5 + 5;

int n, dp[1 << N][N], vis[N], cost[N][N], vv[N];
string s[N], t[N];
char a[N][M];
int n2, id[N], dd[N];

int get(int x, int y) {
	int lenx = strlen(a[x] + 1), leny = strlen(a[y] + 1);
	for (int i = leny; i >= 1; i--) {
		int flg = 0;
		for (int j = 1; j <= i; j++) {
			if (lenx - i + j < 0) {
				flg = 1;
				break;
			}
			if (a[y][j] != a[x][lenx - i + j]) {
				flg = 1;
				break;
			}
		}
		if (!flg) return i;
	}
	return 0;
}

int check(int x) {
	for (int i = 0; i < n; i++) {
		if (x >> i & 1) if (!vis[i]) return 0;
	}
	return 1;
}

bool cmp(int x, int y) {
	return (int)s[x].size() < (int)s[y].size();
}

signed main() {
	cin >> n;
	for (int i = 0; i < n; i++) cin >> (a[i] + 1);
	for (int i = 0; i < n; i++) {
		int lena = strlen(a[i] + 1);
		for (int j = 1; j <= lena; j++) s[i] += a[i][j];
	}
	for (int i = 0; i < n; i++) dd[i] = i;
	sort(dd, dd + n, cmp);
	for (int i = 0; i < n; i++) {
		int flg = 0;
		for (int j = i + 1; j < n; j++) {
			if (s[dd[j]].find(s[dd[i]]) != string::npos) {
				flg = 1;
				break;
			}
		}
		if (!flg) {
			for (int j = 0; j < s[dd[i]].size(); j++) t[n2].push_back(s[dd[i]][j]);
			id[n2] = dd[i], n2++;
		}
	}
	for (int i = 0; i < n2; i++) {
		for (int j = 0; j < n2; j++) if (id[i] != -1 && id[j] != -1 ) cost[i][j] = get(id[i], id[j]);
	}
	memset(dp, 0x3f, sizeof dp);
	for (int i = 0; i < n2; i++) dp[1 << i][i] = t[i].size();
	for (int i = 1; i < (1 << n2); i++) {
		for (int j = 0; j < n2; j++) {
			if (!(i >> j & 1) ) continue;
			for (int k = 0; k < n2; k++) {
				if ((i >> k & 1)) continue;
				dp[i | (1 << k)][k] = min(dp[i | (1 << k)][k], dp[i][j] + (int)t[k].size() - cost[j][k]);
			}
		}
	}
	int res = 2e9;
	for (int i = 0; i < n2; i++) res = min(res, dp[(1 << n2) - 1][i]);
	cout << res << endl;
}
posted @ 2024-03-03 19:37  Otue  阅读(27)  评论(0编辑  收藏  举报