1. 程式人生 > 其它 >AtCoder Beginner Contest 215

AtCoder Beginner Contest 215

AtCoder

AtCoder Beginner Contest 215

菜的本質暴露的一覽無餘(

一場 ABC 一場 ARC 排名都 1k+,直接掉了 200 多分 QwQ

H 是比較神仙的數數題,所以鍋了。

\(A\sim D\)

\(A\sim C\) 就簡單模擬,\(D\) 簡單篩一下。

\(E\)

給定字串序列,只包含前 \(10\) 個大寫字母 \(A\sim J\),求合法的子序列個數。

一個子序列是合法的,當且僅當它當中的相同字元都是連續的,例如 AABCD 合法,但 ABCDA 不合法。

真·考場降智,認為 \(2^{10}\) 超級大,根本不可能狀壓/fad

顯然就簡單狀壓 DP,設 \(f(i,S,x)\)

表示 \(1\sim i\) 中,出現過的字元集合為 \(S\),最後一個字元為 \(x\) 的子序列個數。

簡單轉移即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;
const int N = 1010;
const LL MOD = 998244353;
int n; char str[N];
LL f[N][1 << 10][10];

void P(LL &x, LL y) {x = (x + y) % MOD;}

int main() {
	scanf("%d %s", &n, str + 1);
	for(int i = 1; i <= n; i ++) {
		int c = str[i] - 'A';
		for(int s = 0; s < (1 << 10); s ++)
			for(int t = 0; t < 10; t ++) if(f[i - 1][s][t]) {
				P(f[i][s][t], f[i - 1][s][t]);
				if(!(s >> c & 1) || (t == c)) 
					P(f[i][s | (1 << c)][c], f[i - 1][s][t]);
			}
		P(f[i][1 << c][c], 1);
	}
	LL ans = 0;
	for(int s = 0; s < (1 << 10); s ++)
			for(int t = 0; t < 10; t ++) P(ans, f[n][s][t]);
	printf("%lld\n", ans);
	return 0;
}

\(F\)

求平面最遠點對,其中兩點 \((x_1,y_1),(x_2,y_2)\) 距離定義為 \(\min(|x_1-x_2|,|y_1-y_2|)\)

因為沒想出來 E 所以比賽後期思維挺亂的,跳到這道題之後一直想著分治做法。

實際上是簡單的二分答案,將點按 \(x\) 升序排序,然後雙指標掃描一遍。

得到裡當前點 \(x\) 的差距最小的,\(x\) 維上的距離 \(\geq mid\) 的點,預處理一個 \(y\) 的字尾 \(\min,\max\) 即可簡單判斷。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 2e5 + 10;
int n, Mx[N], Mn[N];
struct Node{int x, y;} a[N];

int read() {
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

bool cmp(Node a, Node b) {return a.x < b.x;}
int Abs(int x) {return x < 0 ? - x : x;}

bool Chck(int mid) {
	int r = 1;
	for(int l = 1; l <= n; l ++) {
		while(r <= n && a[r].x - a[l].x < mid) r ++;
		if(r >  n) return false;
		if(r <= n) {
			if(Abs(a[l].y - Mx[r]) >= mid) return true;
			if(Abs(a[l].y - Mn[r]) >= mid) return true;
		}
	}
	return false;
}

int main() {
	n = read();
	for(int i = 1; i <= n; i ++) a[i].x = read(), a[i].y = read();
	sort(a + 1, a + n + 1, cmp);
	Mx[n] = Mn[n] = a[n].y;
	for(int i = n - 1; i >= 1; i --)
		Mx[i] = max(Mx[i + 1], a[i].y), 
		Mn[i] = min(Mn[i + 1], a[i].y);
	int L = 0, R = Mx[1] - Mn[1];
	while(L < R) {
		int mid = (L + R + 1) >> 1;
		if(Chck(mid)) L = mid; else R = mid - 1;
	}
	printf("%d\n", L);
	return 0;
}

\(G\)

給定長度為 \(N\) 的序列,每個點有顏色 \(c_i\),對於每個 \(1\leq K\leq N\),求隨機選出 \(K\) 個點的期望不同顏色個數。

根據期望的線性性質,可以將問題變為每種顏色選或不選,形式化一點:

\(X(i)\) 表示是否出現了顏色 \(i\),出現了為 \(1\) 否則為 \(0\)

\[Ans=E(\sum\limits_{i=1}^c X(i))=\sum\limits_{i=1}^cE(X(i)) \]

每個 \(E(X(i))\) 相當於顏色 \(i\) 在隨機選的 \(K\) 個點中至少出現一次的概率,設顏色 \(i\) 的出現次數為 \(n_i\),那麼:

\[E(X(i))=(\binom{N}{K}-\binom{N-n_i}{K})/\binom{N}{K} \]

那麼每次的就有:

\[Ans=\frac{\sum\limits_{i=1}^c (\binom{N}{K}-\binom{N-n_i}{K})}{\binom{N}{K}} \]

對於每個 \(K\),都需要 \(O(c)\) 的時間計算,時間複雜度最劣為 \(O(n^2)\)

繼續分析性質,不難發現 \(\sum\) 裡的值在 \(K\) 確定的情況下,只和 \(n_i\) 的值有關。

那麼考慮將 \(n_i\) 相等的顏色縮為同種顏色,同時計算,設最後剩下 \(C\) 種顏色,第 \(i\) 種顏色都由原本的 \(a_i\) 種縮成。

那麼有:

\[Ans=\frac{\sum\limits_{i=1}^C a_i(\binom{N}{K}-\binom{N-n_i}{K})}{\binom{N}{K}} \]

這個看上去沒啥用,但是嚴謹分析一下 \(C\) 的數量級別。

對於 \(n_i>\sqrt N\) 的顏色,顯然至多有 \(\sqrt N\) 種,否則總個數就 \(>N\) 了,顯然不可能。

對於 \(n_i\leq \sqrt N\) 的顏色,至多也就 \(0\sim \sqrt N\) 這麼多種。

故總個數不可能超過 \(2\sqrt N\),而且遠小於這個上界,故總時間複雜度 \(O(N\sqrt N)\)

小插曲:有人在 AtCoder 上釋出了 \(O(n\log n)\) 的 Poly 做法,把標算爆了,但是看不懂。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;

#define X first
#define Y second
#define MP make_pair
typedef pair<int, int> PII;

typedef long long LL;
const int N = 5e4 + 10;
const LL MOD = 998244353;
int n, t, a[N], b[N], sum[N], num[N];
LL fac[N], inv[N];
vector<PII> v;

int read() {
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

LL Pow(LL a, LL b){
	LL sum = 1;
	for(; b; b >>= 1){
		if(b & 1) sum = sum * a % MOD;
		a = a * a % MOD;
	}
	return sum;
}

void Get_Inv(){
	int t = N - 10;
	fac[0] = 1;
	for(int i = 1; i <= t; i ++) fac[i] = fac[i - 1] * i % MOD;
	inv[t] = Pow(fac[t], MOD - 2);
	for(int i = t - 1; i >= 1; i --) inv[i] = inv[i + 1] * (i + 1) % MOD;
}

LL C(int n, int m){
	if(n < m) return 0;
	if(m == 0 || n == m) return 1; 
	return fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}

int main() {
	Get_Inv(); n = read();
	for(int i = 1; i <= n; i ++) a[i] = b[i] = read();
	sort(b + 1, b + n + 1);
	t = unique(b + 1, b + n + 1) - (b + 1);
	for(int i = 1; i <= n; i ++) {
		a[i] = lower_bound(b + 1, b + t + 1, a[i]) - b;
		sum[a[i]] ++;
	}
	for(int i = 1; i <= t; i ++) num[sum[i]] ++;
	for(int i = 1; i <= n; i ++) 
		if(num[i]) v.push_back(MP(i, num[i]));
	for(int k = 1; k <= n; k ++) {
		LL ans = 0;
		for(int i = 0; i < (int) v.size(); i ++)
			ans = (ans + v[i].Y * (C(n, k) + MOD - C(n - v[i].X, k)) % MOD) % MOD;
		ans = ans * Pow(C(n, k), MOD - 2) % MOD;
		printf("%lld\n", ans);
	}
	return 0;
}