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)。(Lleq 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 << '
';
for(int u = 1; u <= n; u++) {
for(auto it : g[u]) {
int v = it.first, w = it.second;
cout << u << ' ' << v << ' ' << w << '
';
}
}
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 {cntchoose j}{n-2j+k-1choose k-1})。
-
后面的组合数就是隔板法的运用,我们知道所有点数出现次数之和为(n),那么就相当于求(x_1+x_2+cdots +x_k=n,x_igeq 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 << '
';
}
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;
}