Educational Codeforces Round 78 (Rated for Div. 2)
A. Shuffle Hashing
签到。
Code
/*
* Author: heyuhhh
* Created Time: 2019/12/21 10:03:44
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '\n'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 100 + 5;
char s[N], t[N];
int pre[26];
int sum[N][26];
void run(){
memset(pre, 0, sizeof(pre));
cin >> (s + 1) >> (t + 1);
int n = strlen(s + 1);
for(int i = 1; i <= n; i++) ++pre[s[i] - 'a'];
int m = strlen(t + 1);
for(int i = 1; i <= m; i++) {
for(int j = 0; j < 26; j++) sum[i][j] = sum[i - 1][j];
++sum[i][t[i] - 'a'];
}
for(int i = 1; i <= m; i++) {
for(int j = i; j <= m; j++) {
static int cnt[26];
for(int k = 0; k < 26; k++) cnt[k] = sum[j][k] - sum[i - 1][k];
bool flag = false;
for(int k = 0; k < 26; k++) {
if(cnt[k] != pre[k]) flag = true;
}
if(flag == false) {
cout << "YES" << '\n';
return;
}
}
}
cout << "NO" << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
int T; cin >> T;
while(T--) run();
return 0;
}
B. A and B
先全部加到较小的一方,然后考虑怎么取出一部分填另一方使得两者相等。
C. Berry Jam
题意:
现在有\(2n\)个糖果,每个糖果有红蓝两者颜色。
现在你位于中间的位置,左边\(n\)个糖果,右边\(n\)个糖果。每次只能吃连续的一段糖果。
现在回答最少吃多少个糖果,能使得剩下的糖果红蓝两种颜色的数量相等。
思路:
- 考虑枚举一半,然后快速在另一半中找到答案。
- 假设我们用\((x,y)\)表示红蓝两种颜色的数量,容易发现\(x,y\)的变化是连续的。
- 假设我们目前枚举到了\(i\),并且将\(n+1\)~\(i\)的糖果都吃完,分别剩下\(r,b\)红、蓝糖果。
- 若\(r>b\),那么就需要找到第一次\(d=r-b\)的位置,\(d=x-y\);否则,就需要找到第一次\(d=b-r\)的位置,\(d=y-x\)。
- 那么事先从\(n\)到\(1\)扫一遍,存储一下第一次出现的位置即可。
- 另外要记得排除一些特殊情况,比如只吃左半部分的糖果这种以及一个糖果都不吃。
- 时间复杂度\(O(n)\)。
代码如下:
Code
/*
* Author: heyuhhh
* Created Time: 2019/12/21 10:16:12
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '\n'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 2e5 + 5;
int n;
int a[N];
int x[N], y[N];
int q[N], top;
int q2[N], top2;
int find(int A, int B) {
if(A == B) return n + 1;
if(A > B) return q2[A - B];
return q[B - A];
}
void run(){
cin >> n;
top = top2 = 0;
for(int i = 1; i <= 2 * n; i++) q[i] = q2[i] = 0;
for(int i = 1; i <= 2 * n; i++) cin >> a[i];
int r = 0, b = 0;
for(int i = 1; i <= 2 * n; i++) {
if(a[i] == 1) ++r; else ++b;
}
x[n + 1] = y[n + 1] = 0;
for(int i = n; i >= 1; i--) {
x[i] = x[i + 1]; y[i] = y[i + 1];
if(a[i] == 1) ++x[i]; else ++y[i];
if(x[i] - y[i] > x[q2[top2]] - y[q2[top2]]) q2[++top2] = i;
if(y[i] - x[i] > y[q[top]] - x[q[top]]) q[++top] = i;
}
int ans = 2 * n;
int curr = 0, curb = 0;
for(int i = n + 1; i <= 2 * n; i++) {
if(a[i] == 1) ++curr;
else ++curb;
int nr = r - curr, nb = b - curb;
int p = find(nr, nb);
if(p) ans = min(ans, i - p + 1);
}
curr = curb = 0;
for(int i = n; i >= 1; i--) {
if(a[i] == 1) ++curr;
else ++curb;
if(r - curr == b - curb) {
ans = min(ans, n - i + 1);
}
}
if(r == b) ans = 0;
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
int T; cin >> T;
while(T--) run();
return 0;
}
D. Segment Tree
题意:
给出\(n\)个区间,每个区间坐标范围为\([1,2n]\),并且保证区间起点终点互不相等。
现在若存在两个区间相交(不包含、包含不算),那么对应的点将会有一条边。
现在判断最终\(n\)个点形成的图是否为一棵树。
思路:
对于树的判定,我们只要保证图连通且有\(n-1\)条边即可。
这一条件等价于存在\(n-1\)条边并且没有环。
那么我们用并查集维护点之间的关系,同时判断一下是否存在环即可。
一开始直接将当前区间和\(set\)中的元素进行合并,T了。之后发现其实左端点在当前区间左边的区间无用,所以直接二分到相交的区间合并即可。
因为至多会合并\(n\)次,所以复杂度为\(O(nlogn)\)。
Code
/*
* Author: heyuhhh
* Created Time: 2019/12/21 11:16:58
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '\n'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e6 + 5;
int L[N], R[N];
struct seg {
int l, r;
bool operator < (const seg &A) const {
return l < A.l;
}
}a[N];
int n, tot;
set <pii> s;
int f[N];
int find(int x) {return f[x] == x ? f[x] : f[x] = find(f[x]);}
int Union(int x, int l) {
int res = 0;
auto p = s.lower_bound(MP(l, 0));
while(p != s.end()) {
int y = p -> second;
int fx = find(x), fy = find(y);
if(fx == fy) return -1;
f[fx] = fy;
if(++tot >= n) return -1;
++p;
}
return 1;
}
void run(){
for(int i = 1; i <= 2 * n; i++) f[i] = i;
for(int i = 1; i <= n; i++) cin >> a[i].l >> a[i].r;
for(int i = 1; i <= n; i++) L[a[i].l] = R[a[i].r] = i;
for(int i = 1; i < N; i++) {
if(L[i]) s.insert(MP(a[L[i]].l, L[i]));
if(R[i]) {
int id = R[i];
s.erase(MP(a[id].l, id));
int t = Union(id, a[id].l);
if(t == -1) {
cout << "NO" << '\n';
return;
}
}
}
if(tot == n - 1) cout << "YES" << '\n';
else cout << "NO" << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
while(cin >> n) run();
return 0;
}
E. Tests for problem D
题意:
给出一棵树,要构造出对应的\(n\)个区间,使得满足D题的条件(就是给D造数据)。
思路:
- 先确定儿子的左端点,然后即可确定当前结点的右端点。
- 之后递归下去依次处理。
就没了...
但直接这样没能保证儿子之间互不相交。
观察到确定左端点时是从左到右逐个确定,那么确定右端点时我们从右往左逐个确定,就能使得儿子区间互相包含了。
细节详见代码:
Code
/*
* Author: heyuhhh
* Created Time: 2019/12/22 16:21:48
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '\n'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e6 + 5;
vector <int> G[N];
int n;
int L[N], R[N];
int tot;
void dfs(int u, int fa) {
for(auto v : G[u]) if(v != fa) {
L[v] = ++tot;
}
R[u] = ++tot;
for(int i = sz(G[u]) - 1; i >= 0; i--) {
int v = G[u][i];
if(v != fa) dfs(v, u);
}
}
void run(){
for(int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
L[1] = ++tot;
dfs(1, 0);
for(int i = 1; i <= n; i++) {
cout << L[i] << ' ' << R[i] << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
while(cin >> n) run();
return 0;
}
F. Cards
题意:
求
其中\(P=\frac{1}{m},n\leq 10^9,k\leq 5000\)。
思路:
直接用第二类斯特拉数将幂次展开并化简:
前面都是套路,最后两步较为巧妙,变换求和范围过后将乘积的形式化为了二项式定理展开的形式,然后直接将和式化简。
之后我们需要做的就是直接预处理第二类斯特林数即可。
代码如下:
Code
/*
* Author: heyuhhh
* Created Time: 2019/12/21 12:40:57
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '\n'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 5005, MOD = 998244353;
int n, m, k;
int fac[N], S[N][N], C[N];
ll qpow(ll a, ll b) {
ll ans = 1;
while(b) {
if(b & 1) ans = ans * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return ans;
}
void run(){
S[0][0] = 1;
for(int i = 1; i <= k; i++) {
for(int j = 1; j <= i; j++) {
S[i][j] = (1ll * S[i - 1][j] * j % MOD + S[i - 1][j - 1]) % MOD;
}
}
C[0] = 1;
for(int i = 1; i < N; i++) C[i] = 1ll * C[i - 1] * (n - i + 1) % MOD;
int invm = qpow(m, MOD - 2);
int ans = 0;
for(int i = 0; i <= k; i++) {
ans = (ans + 1ll * C[i] * S[k][i] % MOD * qpow(invm, i) % MOD) % MOD;
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
while(cin >> n >> m >> k) run();
return 0;
}
重要的是自信,一旦有了自信,人就会赢得一切。