Educational Codeforces Round 86 (Rated for Div. 2)
A. Road To Zero
贪心。
Code
/*
* Author: heyuhhh
* Created Time: 2020/4/26 22:36:10
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#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 << std::endl; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5;
int x, y, a, b;
void run() {
cin >> x >> y >> a >> b;
if(x > y) swap(x, y);
ll ans = 0;
if(1ll * x * y < 0) {
ans += 1ll * (-x) * a + 1ll * y * a;
} else if(1ll * x * y == 0) {
ans += 1ll * y * a;
} else {
int d = abs(x - y);
ans += 1ll * d * a;
if (x < 0) x += d;
else y -= d;
x = abs(x);
ll res = min(1ll * x * b, 2ll * x * a);
ans += res;
}
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;
}
B. Binary Period
像构造\(010101...\)这样构造即可。
Code
/*
* Author: heyuhhh
* Created Time: 2020/4/26 22:45:50
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#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 << std::endl; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5;
void run() {
string s; cin >> s;
int len = s.length();
int cnt = 0;
for (int i = 0; i < len; i++) {
if (s[i] == '0') ++cnt;
}
if (cnt == 0 || cnt == len) {
cout << s << '\n';
return;
}
int cnt0 = cnt, cnt1 = len - cnt;
string res = "";
for (int i = 0; i < len; i++) {
res += "01";
}
cout << res << '\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;
}
C. Yet Another Counting Problem
题意:
给出\(a,b\leq 200\),然后给出\(q,q\leq 500\)个询问,每组询问给出区间\([l,r],l,r\leq 10^{18}\),求出满足\(l\leq x\leq r\)且\(((x \bmod a) \bmod b) \ne ((x \bmod b) \bmod a)\)。
思路:
显然询问我们可以拆分为两个前缀的询问。
对于题目中给出的式子,现在\(x\)以\(a\cdot b\)为一个周期,我们用数组\(sum\)记录一个周期中的前缀信息:\(sum_i\)表示\(0\)~\(i\)中有多少个数不满足上述条件。
对于一次询问\(x\),我们将\([0,x]\)拆分为若干个\([0,a\cdot b-1]\)这样的区间和剩下的区间,直接利用预处理信息计算即可。
代码如下:
Code
/*
* Author: heyuhhh
* Created Time: 2020/4/27 16:18:53
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#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 << std::endl; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5;
int a, b, q;
int sum[N];
void run() {
cin >> a >> b >> q;
int n = a * b;
for (int i = 0; i < n; i++) {
if (i) sum[i] = sum[i - 1];
if ((i % a) % b != (i % b) % a) ++sum[i];
}
auto calc = [&](ll r) {
return r / n * sum[n - 1] + sum[r % n];
};
while (q--) {
ll l, r; cin >> l >> r;
ll ans = calc(r) - calc(l - 1);
cout << ans << ' ';
}
cout << '\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;
}
Bonus: \(a,b\)限制改为\(a,b\leq 10^9\)。
做法如下:
我们考虑将式子变形(不妨\(a<b\)):
此时\(0\leq x\leq r\),我们处理一个询问的答案。
观察上面的式子,只有左边为\(lcm(a,b)\)的倍数时才为合法答案,那么假设左边为\(t\)倍\(lcm\),就有\(\displaystyle b\cdot \lfloor\frac{x}{b}\rfloor=t\cdot \frac{ab}{g}\),化简为\(\displaystyle \lfloor\frac{x}{b}\rfloor=t\cdot \frac{a}{g}=t\cdot a'\)。
显然\(t\)的个数容易求,即为\(\displaystyle \frac{\lfloor\frac{x}{b}\rfloor}{a'}+1\),当然对于每个\(t\),\(x\)有\(b\)种取值情况。
同时要注意一些边界情况,\(x\)不一定刚好取满\(b\)。
细节见代码:
Code
/*
* Author: heyuhhh
* Created Time: 2020/4/27 17:26:05
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#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 << std::endl; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5;
void run() {
int a, b, q;
cin >> a >> b >> q;
if (a > b) swap(a, b);
int g = __gcd(a, b);
a /= g;
auto calc = [&] (ll x) {
ll now = x / b;
ll res = (now / a + 1) * b;
if (now % a == 0) {
(res -= b) += x % b + 1;
}
return res;
};
while (q--) {
ll l, r; cin >> l >> r;
ll ans = r - l + 1;
ans -= calc(r) - calc(l - 1);
cout << ans << ' ';
}
cout << '\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. Multiple Testcases
题意:
给出\(n\)个数,每个数权值不超过\(k\),现在要从这些数中选出一些组成若干个序列,要求每个数恰好被选入一个序列之中。
并且满足限制:\(c_i\)表示不小于\(i\)的数的个数,对于\(1\leq i\leq k\)都要满足。
现在问最少构成多少个序列。
保证\(c_1\geq c_2\geq \cdots \geq c_k\)。
思路:
最简单的想法,我们从后往前贪心来选,每次找到一个合适的位置然后将其删除添加入序列中,反复执行这个过程即可。
因为我们需要查找对应位置,并且要删除操作,所以用个\(set\)维护原序列剩下的数。
时间复杂度\(O(nlog^2n)\)。
代码如下:
Code
/*
* Author: heyuhhh
* Created Time: 2020/4/26 23:46:45
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#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 << std::endl; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 2e5 + 5;
int n, k;
int m[N], c[N];
void run() {
cin >> n >> k;
multiset <int> s;
for (int i = 1; i <= n; i++) {
cin >> m[i];
s.insert(m[i]);
}
for (int i = 1; i <= k; i++) {
cin >> c[i];
}
vector <vector<int>> ans;
while(sz(s)) {
vector <int> res;
while(1) {
int l = 1, r = k + 1, mid;
auto chk = [&] (int x) {
auto it = s.lower_bound(x);
if(it != s.end() && c[*it] >= sz(res) + 1) {
return true;
}
return false;
};
while(l < r) {
mid = (l + r) >> 1;
if(chk(mid)) l = mid + 1;
else r = mid;
}
auto it = s.lower_bound(l - 1);
if(it == s.end() || *it != l - 1) break;
s.erase(it);
res.push_back(l - 1);
}
ans.push_back(res);
}
cout << sz(ans) << '\n';
for (int i = 0; i < sz(ans); i++) {
cout << sz(ans[i]);
for (auto it : ans[i]) {
cout << ' ' << it;
}
cout << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
run();
return 0;
}
这个题的时间复杂度还可以进一步优化。
我们注意到假设最后的答案为\(k\),那么将原数组像\(1,2,\cdots,k,1,2\cdots,k,1,\cdots\)这样进行分组肯定符合条件。
所以我们二分答案,直接将原序列像上面进行分组然后check即可,时间复杂度为\(O(nlogn)\)。
这个题还可以做到\(O(n)\)。我们现在只需要快速求出最小的\(k\)即可。
那么遍历一遍,对每个位置求出按照当前位置作为开头最小的分段数,这里面取最大值即是答案。
假设\(i\)得到\(x\),\(j,i<j\)得到\(y\),若\(y\leq x\),那么分为\(x\)组也可以满足条件;否则分\(y\)组才行,分\(y\)组也满足\(i\)位置的限制。
代码如下:
Code
/*
* Author: heyuhhh
* Created Time: 2020/4/27 16:10:31
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#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 << std::endl; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 2e5 + 5;
int n, k;
int m[N], c[N], cnt[N], suf[N];
void run() {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> m[i];
++cnt[m[i]];
}
sort(m + 1, m + n + 1);
for (int i = k; i >= 1; i--) {
suf[i] = suf[i + 1] + cnt[i];
}
for (int i = 1; i <= k; i++) {
cin >> c[i];
}
int res = 0;
for (int i = 1; i <= k; i++) {
res = max(res, (suf[i] - 1) / c[i] + 1);
}
cout << res << '\n';
vector <vector <int>> ans(res);
for (int i = 1; i <= n; i++) {
ans[(i - 1) % res].push_back(m[i]);
}
for (int i = 0; i < res; i++) {
cout << sz(ans[i]);
for (auto it : ans[i]) cout << ' ' << it;
cout << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
run();
return 0;
}
感觉\(O(nlogn)\)的做法是最自然的,“性价比”也比较高。
E. Placing Rooks
题意:
给出一个\(n\cdot n\)的棋盘,现在要在棋盘上放置\(n\)个棋子,每个棋盘能够攻击到在同一行、同一列的格子。
现在问满足如下条件的棋子放置总情况个数:
- 棋盘每个格子都能被攻击到;
- 恰好有\(k\)对棋子能互相攻击,一对棋子能互相攻击的条件是这对棋子位于同一行或者同一列,并且中间没有其它棋子。
比如下图为\(n=3,k=2\)的一种合法情况:
思路:
因为保证每个格子都处于攻击之下,所以每一行/每一列都必须有一个棋子,我们接下来考虑行的情况,列的情况同理。
注意到有这样一个事实:
- 在每一行放置一个棋子的情况下,互相攻击的棋子必须在同一列,并且将\(n\)个棋子放入\(t\)列中,每一列至少有一个,总的攻击的对数为\(n-t\)。
挺容易证明的,假设现在\(t\)列共有\(k\)对棋子互相攻击,那么有\(x_1+x_2+\cdots+x_t=k\),令\(y_i=x_i+1\),那么\(y_i\)即是第\(i\)列的棋子个数,所以就有\(y_1+y_2+\cdots+y_t=n\)。现在已知\(y_1+y_2+\cdots+y_t\),那么很容易推出\(x_1+x_2+\cdots+x_t\)。
那么此时我们所求的方案数为\(\displaystyle {n\choose n-k}\cdot \begin{Bmatrix}
n \\ n-k
\end{Bmatrix}\cdot(n-k)!\)。
后者为第二类斯特林数,因为我们这里不同的顺序会算上所有情况,那么最后还要乘以一个阶乘。
第二类斯特林数可以直接通过容斥来求,可以参见我之前写的博客。
代码如下:
Code
/*
* Author: heyuhhh
* Created Time: 2020/4/27 0:38:49
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#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 << std::endl; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 2e5 + 5, MOD = 998244353;
int qpow(ll a, ll b) {
ll res = 1;
while(b) {
if(b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
ll n, k;
int fac[N], inv[N];
int C(int n, int m) {
return 1ll * fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}
void run() {
cin >> n >> k;
if (k >= n) {
cout << 0 << '\n';
return;
}
fac[0] = 1;
for (int i = 1; i < N; i++) fac[i] = 1ll * fac[i - 1] * i % MOD;
inv[N - 1] = qpow(fac[N - 1], MOD - 2);
for (int i = N - 2; i >= 0; i--) inv[i] = 1ll * inv[i + 1] * (i + 1) % MOD;
int ans = 0;
for (int i = 0; i <= n - k; i++) {
int res = 1ll * C(n - k, i) * qpow(n - k - i, n) % MOD;
if(i & 1) res = MOD - res;
ans = (ans + res) % MOD;
}
ans = 1ll * ans * C(n, n - k) % MOD;
if(k) (ans <<= 1) %= MOD;
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
run();
return 0;
}
重要的是自信,一旦有了自信,人就会赢得一切。