AtCoder Regular Contest 102
C - Triangular Relationship
题意:
给出\(n,k\),现在要你求合法三元组的数量,合法是指对于一个三元组\((a,b,c)\),每个数都不超过\(n\),并且\(a+b,b+c,a+c\)为\(k\)的倍数。
思路:
按照模\(k\)的余数来讨论即可。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, k;
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> k;
ll ans = n / k;
ans = ans * ans * ans;
ll tmp = n / k + (n % k >= k / 2);
if(k % 2 == 0) ans += tmp * tmp * tmp;
cout << ans;
return 0;
}
D - All Your Paths are Different Lengths
题意:
给出\(L\),先要求构造一个点数不超过\(20\),边数不超过\(60\)的有向图,并且满足从起点\(1\)到终点\(n\)共有\(L\)条路径,并且路径长度为\(0,1,\cdots,L-1\)。\(L\leq 10^6\)。
思路:
- 考虑二进制拆分。
- 假设\(L\)的二进制的最高位为\(r\),那么我们首先构造出一条链的形式,有\(r\)个结点,并且前一个点向后一个点连边\(0\)和\(2^i(i<r)\)。
- 这条链即表示最高位为\(0\)时的所有情况。
- 但最高位为\(1\)时怎么办?
- 假设这条链是从\(1\)号点开始,到\(n\)号点结尾,并且边\((i,i+1)\)的边权为\(2^{i-1}\)。那么从点\(i\)连接一条边到\(n\)号结点,边权为\(X\),可以发现会多出一些长度为\(X+0,X+1,\cdots,X+2^{i-1}-1\)的路径。
- 根据这一点选择合适的\(X\)以及结点\(i\)处理即可。
代码如下:
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 66;
int L;
int sum[N];
int n, m;
vector <pair<int, int> > g[N];
void add(int u, int v, int w, int op) {
++m;
g[u].push_back(make_pair(v, w));
if(op) sum[v] = sum[u] + w;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> L;
int r;
for(int i = 30; i >= 0; i--) if(L >> i & 1) {
r = i; break;
}
for(int i = 2; i <= r + 1; i++) {
add(i - 1, i, 1 << (i - 2), 1);
add(i - 1, i, 0, 0);
}
int X = (1 << r); --L;
while(X <= L) {
int p = upper_bound(sum + 1, sum + r + 1, L - X) - sum - 1;
add(p, r + 1, X, 0);
X += sum[p] + 1;
}
n = r + 1;
cout << n << ' ' << m << '\n';
for(int u = 1; u <= n; u++) {
for(auto it : g[u]) {
int v = it.first, w = it.second;
cout << u << ' ' << v << ' ' << w << '\n';
}
}
return 0;
}
E - Stop. Otherwise...
题意:
有\(n\)个骰子,每个骰子上面有点数\(1,2,\cdots,k\)。
先求对于所有\(x=2,3,\cdots,2k\),任意两个骰子的点数之和都不为\(x\)的状态数为多少。
两个状态为不同的方案,当且仅当至少存在一个点数\(i\),在两种方案中的出现次数不同。
思路:
显然可以考虑容斥来搞。
-
对于一个点\(i\),如果出现了,那么说明就不能出现\(x-i\),在每种情况中,都有若干这种pair关系。
-
那么在一种情况中,我们容斥掉所有不合法情况即可。
-
怎么容斥?
-
假设当前有\(cnt\)个\(pair\)为非法的,我们直接枚举选了多少对,那么答案就是\(\sum(-1)^j {cnt\choose j}{n-2j+k-1\choose k-1}\)。
-
后面的组合数就是隔板法的运用,我们知道所有点数出现次数之和为\(n\),那么就相当于求\(x_1+x_2+\cdots +x_k=n,x_i\geq 0\)的解的个数。
这里的隔板法和平时还是有点区别,还是要看具体题目吧,怎样才算不同的情况,以及等式的存在性。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 4005, MOD = 998244353;
int fac[N], inv[N];
int qp(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 pre() {
fac[0] = 1;
for(int i = 1; i < N; i++) fac[i] = 1ll * fac[i - 1] * i % MOD;
inv[N - 1] = qp(fac[N - 1], MOD - 2);
for(int i = N - 2; ~i; i--) inv[i] = 1ll * inv[i + 1] * (i + 1) % MOD;
}
ll C(int n, int m) {
if(n < m) return 0;
return 1ll * fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}
int n, k;
int tot[N];
int main() {
ios::sync_with_stdio(false); cin.tie(0);
pre();
cin >> k >> n;
for(int i = 1; i <= k; i++) {
++tot[i + 1]; --tot[i + k + 1];
}
for(int i = 1; i <= 2 * k; i++) tot[i] += tot[i - 1];
for(int i = 2; i <= 2 * k; i++) {
ll ans = 0;
int cnt = (tot[i] + 1) / 2;
for(int j = 0, d = 1; j <= cnt; j++, d = MOD - d) {
ans += 1ll * d * C(cnt, j) % MOD * C(n - 2 * j + k - 1, k - 1) % MOD;
ans %= MOD;
}
if(ans < 0) ans += MOD;
cout << ans << '\n';
}
return 0;
}
F - Revenge of BBuBBBlesort!
题意:
给出一个\(1\)到\(n\)的排列,现在你可以执行操作任意多次,该操作为:
- 选择\(3\)个元素,满足\(a_{i-1}>a_{i}>a{i+1}\),那么可以将这三个数翻转。
问最后是否能将该排列变为\(1,2,\cdots,n\)。
思路:
很考察分析和观察能力的一个题。
我们可以考虑一个逆过程,就是对于\(1,2,\cdots,n\),我们可以选择递增的三个数,将其翻转。
那么就有如下几个观察:
- 观察1:如果以\(i\)为中心进行了一次翻转,那么\(a_i\)则不会发生改变。
- 观察2:原问题可以划分为若干个独立的区间,每个区间我们可以单独处理。假设这个区间为\([l,r]\),那么\(l+1,l+3,\cdots,r-1\)这些位置的数不会发生改变。
- 观察3:对于\(a_i\),若其先向左移动,则不能再向右移动,并且可以引申:向左移动的数的相对位置不会改变。向右移动同理。
这些观察的具体证明...第一个和第三个很好证,第二个感性理解一下?
然后这个题就可以这样直接搞就行了。细节见代码吧。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 5;
int n;
int a[N];
void YES() {
cout << "Yes"; exit(0);
}
void NO() {
cout << "No"; exit(0);
}
int tmp[N], cnt;
int pos[N], dir[N];
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
int l = 1, r;
while(1) {
while(l <= n && a[l] == l) ++l;
if(l > n) YES();
r = l;
while(1) {
if((r - l + 1) & 1) {
if(a[r] != r) ++r;
else break;
} else {
if(a[r] == r) ++r;
else break;
}
if(r > n) break;
}
--r; if((r - l) & 1) --r;
if(l == r) NO();
cnt = 0;
for(int i = l; i <= r; i++) tmp[++cnt] = a[i];
sort(tmp + 1, tmp + cnt + 1);
for(int i = 1; i <= cnt; i++) if(tmp[i] != i + l - 1) NO();
cnt = 0;
for(int i = l; i <= r; i += 2) {
tmp[++cnt] = a[i]; pos[a[i]] = i;
}
sort(tmp + 1, tmp + cnt + 1);
for(int i = 1; i <= cnt; i++) {
dir[pos[tmp[i]]] = 2 * i + l - 2;
}
int lastl = 0, lastr = n + 1;
for(int i = l; i <= r; i += 2) {
if(dir[i] > i) continue;
if(dir[i] > lastl) lastl = dir[i];
else NO();
}
for(int i = r; i >= l; i -= 2) {
if(dir[i] < i) continue;
if(dir[i] < lastr) lastr = dir[i];
else NO();
}
l = r + 1;
}
return 0;
}
重要的是自信,一旦有了自信,人就会赢得一切。