【CodeForces】Hello 2019 (Div. 1 + Div. 2) 题解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_39972971/article/details/86145167

【比赛链接】

【题解链接】

**【A】**Gennady and a Card Game

【思路要点】

  • 按照题意模拟。
  • 时间复杂度 O ( 1 ) O(1)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
string x, y;
int main() {
	cin >> x;
	bool ans = false;
	for (int i = 1; i <= 5; i++) {
		cin >> y;
		ans |= y[0] == x[0] || y[1] == x[1];
	}
	if (ans) puts("YES");
	else puts("NO");
	return 0;
}

**【B】**Petr and a Combination Lock

【思路要点】

  • 枚举每一次旋转的方向。
  • 时间复杂度 O ( 2 N ) O(2^N)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, a[MAXN];
bool ans;
void work(int pos, int sum) {
	if (pos == n + 1) ans |= sum % 360 == 0;
	else {
		work(pos + 1, sum + a[pos]);
		work(pos + 1, sum - a[pos]);
	}
}
int main() {
	read(n);
	for (int i = 1; i <= n; i++)
		read(a[i]);
	work(1, 0);
	if (ans) puts("YES");
	else puts("NO");
	return 0;
}

**【C】**Yuhao and a Parenthesis

【思路要点】

  • 将一个括号序列的 ( ( 看做 + 1 +1 ) ) 看做 1 -1 ,我们可以用二元组 ( s u m , M i n ) (sum,Min) 来描述一个括号序列,其中 s u m sum 表示序列和, M i n Min 表示前缀最小值。
  • s u m 0 sum≤0 ,要求 M i n = s u m Min=sum ,否则,要求 M i n = 0 Min=0
  • 满足上述条件的 s u m sum 之和为 0 0 的括号序列可以配对,贪心即可。
  • 时间复杂度 O ( s i ) O(\sum |s_i|)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int cnt[MAXN], cmt[MAXN];
int main() {
	int n; read(n);
	for (int i = 1; i <= n; i++) {
		string x; cin >> x;
		int sum = 0, Min = 0;
		for (auto y : x)
			if (y == '(') sum++;
			else sum--, chkmin(Min, sum);
		if (sum <= 0) {
			if (Min == sum) cnt[-sum]++;
		} else {
			if (Min == 0) cmt[sum]++;
		}
	}
	int ans = cnt[0] / 2;
	for (int i = 1; i <= 5e5; i++)
		ans += min(cnt[i], cmt[i]);
	writeln(ans);
	return 0;
}

**【D】**Makoto and a Blackboard

【思路要点】

  • 可以发现,最后各个质因子 p p 剩余指数为某一值 x x 的概率在不同质因子之间是相互独立的。
  • N N 质因数分解,对各个质因数分别 d p dp ,然后计算 N N 各因数出现的概率即可。
  • 时间复杂度 O ( K L o g 2 N + N ) O(KLog^2N+\sqrt{N})

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int P = 1e9 + 7;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
ll n; int k, num[MAXN];
int m, ans, val[MAXN], inv[MAXN];
int dp[MAXN][100], p[MAXN][100]; 
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
void getans(int pos, int sum, int pro) {
	if (pos == m + 1) {
		update(ans, 1ll * sum * pro % P);
		return;
	}
	for (int i = 0; i <= num[pos]; i++) {
		getans(pos + 1, sum, 1ll * pro * p[pos][i] % P);
		sum = 1ll * sum * val[pos] % P;
	}
}
int main() {
	read(n), read(k);
	for (ll i = 2; i * i <= n; i++)
		if (n % i == 0) {
			int cnt = 0;
			while (n % i == 0) n /= i, cnt++;
			num[++m] = cnt;
			val[m] = i;
		}
	if (n != 1) {
		num[++m] = 1;
		val[m] = n % P;
	}
	for (int i = 1; i <= 64; i++)
		inv[i] = power(i, P - 2);
	for (int i = 1; i <= m; i++) {
		for (int j = 0; j <= k; j++)
		for (int l = 0; l <= num[i]; l++)
			dp[j][l] = 0;
		dp[0][num[i]] = 1;
		for (int j = 1; j <= k; j++)
		for (int l = 0; l <= num[i]; l++) {
			for (int o = 0; o <= l; o++)
				update(dp[j][o], 1ll * dp[j - 1][l] * inv[l + 1] % P);
		}
		for (int j = 0; j <= num[i]; j++)
			p[i][j] = dp[k][j];
	}
	getans(1, 1, 1);
	writeln(ans);
	return 0;
}

**【E】**Egor and an RPG game

【思路要点】

  • 考虑一类输入 { 1 , 3 , 2 , 6 , 5 , 4 , 10 , 9 , 8 , 7 , . . . } \{1,3,2,6,5,4,10,9,8,7,...\} ,此类输入在长度 N N k ( k + 1 ) 2 \frac{k(k+1)}{2} 时可以构造出一组答案为 k k 的输入,于是,一个猜想是 f ( x ) = m a x { k     k Z , k ( k + 1 ) 2 x } f(x)=max\{k\ |\ k\in \Z,\frac{k(k+1)}{2}≤x\}
  • 我们提出一个构造算法来证明上述猜想。
  • 形式化地来说,我们需要证明对于 N &lt; k ( k + 1 ) 2 N&lt;\frac{k(k+1)}{2} ,存在一组 k 1 k-1 的解。
  • 求解输入序列的最长上升子序列,令其长度为 x x
  • x k x≥k ,将该序列删除,递归解决该问题。
  • 否则,根据 D i l w o r t h Dilworth 定理,该序列可以被拆分为 x &lt; k x&lt;k 个下降子序列,并且我们可以在求解最长上升子序列时顺带解决将其拆分为 x x 个下降子序列的问题。
  • 时间复杂度 O ( N N L o g N ) O(N\sqrt{N}LogN)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, a[MAXN]; 
bool vis[MAXN];
vector <vector <int> > ans;
void solve(int k) {
	static vector <int> low[MAXN];
	int Max = 0; low[0].resize(1);
	static int dp[MAXN], from[MAXN];
	for (int i = 1; i <= n; i++) {
		if (vis[a[i]]) continue;
		int l = 0, r = Max + 1;
		while (l < r) {
			int mid = (l + r) / 2;
			if (a[i] > low[mid].back()) l = mid + 1;
			else r = mid;
		}
		dp[a[i]] = l, from[a[i]] = low[l - 1].back();
		if (l > Max) {
			Max = l;
			low[Max].clear();
			low[Max].push_back(a[i]);
		} else low[l].push_back(a[i]);
	}
	if (Max == 0) return;
	if (Max >= k) {
		vector <int> res; res.clear();
		int pos = 0;
		for (int i = 1; i <= n; i++)
			if (!vis[a[i]] && dp[a[i]] == Max) {
				pos = a[i];
				break;
			}
		while (pos) {
			res.push_back(pos);
			vis[pos] = true;
			pos = from[pos];
		}
		reverse(res.begin(), res.end());
		ans.push_back(res);
		solve(k - 1);
	} else {
		for (int i = 1; i <= Max; i++)
			ans.push_back(low[i]);
	}
}
int main() {
	int T; read(T);
	while (T--) {
		read(n);
		for (int i = 1; i <= n; i++) {
			read(a[i]);
			vis[i] = false;
		}
		ans.clear();
		int k = 1;
		while (k * (k + 1) / 2 <= n) k++;
		solve(k);
		writeln(ans.size());
		for (auto x : ans) {
			write(x.size());
			for (auto y : x)
				printf(" %d", y);
			putchar('\n');
		}
	}
	return 0;
}

**【F】**Alex and a TV Show

【思路要点】

  • 如果我们用 b i t s e t bitset 维护集合中的元素,那么操作 1 , 2 , 4 1,2,4 均可以轻松地实现。
  • 考虑操作 3 3 ,通过莫比乌斯反演,结果集合中存在数字 x x 当且仅当 i = 1 v x μ ( i ) s u m A ( i x ) s u m B ( i x ) = 1   ( m o d   2 ) \sum_{i=1}^{\lfloor\frac{v}{x}\rfloor}\mu(i)*sum_A(ix)*sum_B(ix)=1\ (mod\ 2) ,其中 s u m A ( x ) , s u m B ( x ) sum_A(x),sum_B(x) 分别表示 A , B A,B x x 的倍数的个数。
  • 那么,如果我们用 b i t s e t bitset 维护集合中的 s u m sum ,那么操作 2 , 3 2,3 分别都可以用异或、按位与来实现,操作 4 4 的询问可以通过预处理莫比乌斯函数对应的 b i t s e t bitset 来实现。
  • 时间复杂度 O ( q v w ) O(\frac{qv}{w})

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int MAXM = 7e3 + 5;
typedef long long ll;
typedef bitset <MAXM> info;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
info a[MAXN], b[MAXM], getans[MAXM];
int tot, prime[MAXM], f[MAXM], miu[MAXM];
void init(int n) {
	miu[1] = 1;
	for (int i = 2; i <= n; i++) {
		if (f[i] == 0) {
			prime[++tot] = f[i] = i;
			miu[i] = -1;
		}
		for (int j = 1; j <= tot && prime[j] <= f[i]; j++) {
			int tmp = prime[j] * i;
			if (tmp > n) break;
			f[tmp] = prime[j];
			if (prime[j] == f[i]) miu[tmp] = 0;
			else miu[tmp] = -miu[i];
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = i; j <= n; j += i) {
			b[j][i] = 1;
			if (miu[j / i]) getans[i][j] = 1;
		}
	}
}
int main() {
	init(7000);
	int n, m;
	read(n), read(m);
	for (int i = 1; i <= m; i++) {
		int opt, x, y, z;
		read(opt), read(x), read(y);
		if (opt == 1) a[x] = b[y];
		if (opt == 2) {
			read(z);
			a[x] = a[y] ^ a[z];
		}
		if (opt == 3) {
			read(z);
			a[x] = a[y] & a[z];
		}
		if (opt == 4) putchar('0' + (a[x] & getans[y]).count() % 2);
	}
	return 0;
}

**【G】**Vladislav and a Great Legend

【思路要点】

  • x k x^k 写成 i = 0 k S ( k , i ) i ! ( x i ) \sum_{i=0}^{k}S(k,i)*i!*\binom{x}{i} 的形式。
  • 因此,我们需要计算对于每一个点集对应的生成树,从中选出 i   ( i = 0 , 1 , 2 , . . . , k ) i\ (i=0,1,2,...,k) 条边标记的总方案数。
  • 考虑在每一棵生成树深度最低的点处计算其贡献,用树形背包解决即可。
  • 时间复杂度 O ( N k ) O(Nk)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int MAXK = 205;
const int P = 1e9 + 7;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, k, fac[MAXK], s[MAXK][MAXK];
int dp[MAXN][MAXK], res[MAXK], size[MAXN];
vector <int> a[MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
void times(int *a, int &la, int *b, int lb) {
	static int tmp[MAXK];
	memset(tmp, 0, sizeof(tmp));
	for (int i = 0; i <= la && i <= k; i++)
	for (int j = 0; j <= lb && i + j <= k; j++)
		update(tmp[i + j], 1ll * a[i] * b[j] % P);
	memcpy(a, tmp, sizeof(tmp));
	la += lb;
}
void work(int pos, int fa) {
	size[pos] = 1, dp[pos][0] = 2;
	for (auto x : a[pos])
		if (x != fa) {
			work(x, pos);
			times(dp[pos], size[pos], dp[x], size[x]);
			for (int i = 0; i <= k; i++)
				update(res[i], P - dp[x][i]);
		}
	for (int i = 0; i <= k; i++)
		update(res[i], dp[pos][i]);
	for (int i = k; i >= 1; i--)
		update(dp[pos][i], dp[pos][i - 1]);
	update(dp[pos][1], P - 1);
}
void init(int n, int k) {
	s[0][0] = fac[0] = 1;
	for (int i = 1; i <= k; i++) {
		fac[i] = 1ll * fac[i - 1] * i % P;
		for (int j = 1; j <= i; j++)
			s[i][j] = (s[i - 1][j - 1] + 1ll * s[i - 1][j] * j) % P;
	}	
}
int main() {
	read(n), read(k);
	for (int i = 1; i <= n - 1; i++) {
		int x, y; read(x), read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	init(n, k);
	work(1, 0);
	int ans = 0;
	for (int i = 0; i <= k; i++)
		update(ans, 1ll * s[k][i] * fac[i] % P * res[i] % P);
	writeln(ans);
	return 0;
}

**【H】**Mateusz and an Infinite Sequence

【思路要点】

  • 考虑如何描述一个区间里的元素。
  • 我们需要记录其长度 ( l e n ) (len) ,包含可行子串的个数 ( a n s ) (ans) ,其长度为 i i 的后缀是否能作为可行子串长度为 i i 的前缀(没有出现的元素当做 0 0 ( s u f i   ( i = 1 , 2 , 3 , . . . , N 1 ) ) (suf_i\ (i=1,2,3,...,N-1)) ,长度为 N i N-i 的前缀是否能作为可行子串长度为 N i N-i 的后缀(没有出现的元素当做 0 0 ( p r e i   ( i = 1 , 2 , 3 , . . . , N 1 ) ) (pre_i\ (i=1,2,3,...,N-1))
  • 上述信息是可以合并的,并可以利用 b i t s e t bitset 做到 O ( N w ) O(\frac{N}{w}) 合并。
  • 接下来的部分就是一个简单数位 d p dp ,记 d p i , j dp_{i,j} 表示序列的前 d i d^i 位加上 j j 后的信息,可以简单计算 d p dp 值,并求解答案。
  • 时间复杂度 O ( N d M L o g M w ) O(\frac{NdMLogM}{w})

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e4 + 5;
const int MAXD = 25;
const int MAXM = 65;
const int MAXLOG = 64;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
struct info {
	ll ans, len;
	bitset <MAXN> pre, suf;
};
bitset <MAXN> all;
int n, d, m, gen[MAXD], b[MAXN];
int tot; ll len[MAXLOG]; info dp[MAXLOG][MAXM];
info operator + (const info &a, const info &b) {
	if (a.len == 0) return b;
	info res;
	res.len = a.len + b.len;
	res.ans = a.ans + b.ans;
	res.pre = a.pre;
	res.suf = b.suf;
	if (a.len <= n - 2) res.pre &= (b.pre >> a.len) | (all << (n - 1 - a.len));
	if (b.len <= n - 2) res.suf &= (a.suf << b.len) | (all >> (n - 1 - b.len));
	if (a.len + b.len >= n) {
		bitset <MAXN> tmp = a.suf & b.pre;
		if (a.len <= n - 2) tmp &= all >> (n - 1 - a.len);
		if (b.len <= n - 2) tmp &= all << (n - 1 - b.len);
		res.ans += tmp.count(); 
	}
	return res;
}
ll work(ll x) {
	info res;
	res.ans = 0;
	res.len = 0;
	res.pre.reset();
	res.suf.reset();
	int add = 0;
	for (int i = tot; i >= 0; i--) {
		for (int j = 1; j <= d; j++)
			if (x >= len[i]) {
				x -= len[i];
				res = res + dp[i][(add + gen[j]) % m];
			} else {
				add = (add + gen[j]) % m;
				break;
			}
	}
	return res.ans;
}
int main() {
	read(d), read(m);
	for (int i = 1; i <= d; i++)
		read(gen[i]);
	read(n);
	for (int i = 1; i <= n; i++)
		read(b[i]);
	for (int i = 1; i <= n - 1; i++)
		all.set(i);
	tot = 0, len[0] = 1;
	for (int i = 0; i <= m - 1; i++) {
		dp[0][i].len = 1;
		if (n == 1) dp[0][i].ans = i <= b[1];
		else {
			for (int j = 1; j <= n - 1; j++) {
				dp[0][i].pre[j] = i <= b[j + 1];
				dp[0][i].suf[j] = i <= b[j];
			}
		}
	}
	ll ql, qr; read(ql), read(qr);
	while (len[tot] <= qr / d) {
		tot++, len[tot] = len[tot - 1] * d;
		for (int i = 0; i <= m - 1; i++)
		for (int j = 1; j <= d; j++)
			dp[tot][i] = dp[tot][i] + dp[tot - 1][(i + gen[j]) % m];
	}
	writeln(work(qr) - work(ql + n - 2));
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39972971/article/details/86145167