D. New Year and Arbitrary Arrangement
分析
(dp[i][j]) 表示已有 (i) 个 (a) 和 (j) 个 (ab) 的情况下继续构造能得到的 (ab) 个数的期望。
考虑 DFS 记忆化搜索。
有两个要注意的地方:
令 (p_a) 为添加 (a) 的概率,(p_b) 为添加 (b) 的概率。
- 当 (i + j geq k) 时,这个情况下添加一个 (b) 构造就停止了,但是在这个 (b) 之前显然可以无限添加 (a) ,这后面的期望为 ((i + j) * p_b + p_a*(i+j+1)*p_b+p_a^2*(i+j+2)*p_b+...) 这个式子可以化简,(O(1)) 计算。
- 起始状态应该是 (dp[0][0] = dp[1][0] * p_a + dp[0][0] * p_b) ,(dp[0][0] * p_b) 说明我们可以不断添加前导 (b) 。可以发现 (dp[0][0] = (p_b^0+p_b^1+p_b^2...) * dp[1][0] * p_a=frac{1}{1-p_b}*dp[1][0]*p_a=dp[1][0]),所以我们可以直接计算一个以 (a) 开头的序列 ,即计算 (dp[1][0]) 。
code
#include<bits/stdc++.h>
using namespace std;
const int MOD = 1e9 + 7;
long long POW(long long x, int k) {
long long ret = 1;
while(k) {
if(k & 1) ret = (ret * x) % MOD;
x = x * x % MOD;
k >>= 1;
}
return ret;
}
long long k, pa, pb, dp[1001][1001];
long long dfs(int a, int ab) {
if(a + ab >= k) return a + ab + pa * POW(pb, MOD - 2) % MOD;
if(dp[a][ab] != -1) return dp[a][ab];
return dp[a][ab] = (dfs(a + 1, ab) * pa % MOD + dfs(a, ab + a) * pb % MOD) * POW(pa + pb, MOD - 2) % MOD;
}
int main() {
memset(dp, -1, sizeof dp);
cin >> k >> pa >> pb;
long long p = __gcd(pa, pb);
pa /= p;
pb /= p;
cout << dfs(1, 0) << endl;
return 0;
}
E. New Year and Entity Enumeration
分析
官方题解 很详细了,自己补充几点。
- 考虑 (m) 个二进制数,假设 (m=4) ,那么有 0001,0010,0100,1000 。这些数一定是某一二进制位有 (1) 的最小的数,用 (f) 表示这个关系。(f[0]=0001),(f[1]=0010) ... 。这些数中任意两个数按位与后都为 (0) ,那么可以考虑求这个集合划分方法的数目,即贝尔数 ,将各个子集中的数分别按位或起来,举个例子 1000,0100,0011 。这种情况下,第一位和第二位为 (1) 的二进制数都是 0011 ,可以发现,各种划分方案中我们得到的 (f) 并不会完全相同,所以用这个划分方案数就对应着 (m) 位二进制数的解(具体可见官方题解的证明)。
- 题目要求 (T) 是 (S) 的子集。以样例为例,
11010
00101
11000
竖着去看,有两个 101 两个 010 一个 100 。考虑两个 101 ,我们再横着去看,即 00 11 00 ,我们知道一个 (k) 进制数的任意一种方案一定包括 (k) 个 (0) 和 (k) 个 (1) 这两个二进制数,我们分开去求一定可以得到满足题目的方案,若 (b[i]) 为贝尔数,那么这个答案就是 (b[2] * b[2] * b[1])。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
const int MOD = 1e9 + 7;
long long C[N][N];
map<long long, int> mp;
long long a[N], b[N], c[N][N];
int main() {
for(int i = 0; i < N; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++) {
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}
}
int n, m;
cin >> m >> n;
while(n--) {
long long y = 0;
for (int i = 0; i < m; i++) {
long long x;
scanf("%1lld", &x);
a[i] |= x << n;
}
}
for (int i = 0; i < m; i++) {
mp[a[i]]++;
}
b[0] = b[1] = 1;
for (int i = 2; i <= m; i++) {
for (int j = 0; j < i; j++) {
(b[i] += C[i - 1][j] * b[i - j - 1]) %= MOD;
}
}
long long ans = 1;
for(auto it : mp) {
(ans *= b[it.second]) %= MOD;
}
cout << ans << endl;
return 0;
}
F. New Year and Rainbow Roads
分析
以 G 为间隔分组,每个组内单独讨论,要满足题目条件有两种连法:
- 两个 G 不直接相连,通过连接 B 间接连上,也要通过连接 R 间接连上,花费为两倍两个 G 直接的距离。
- 两个 G 直接相连,那么 B 全部连上的话就成了一个环了,考虑去掉花费最大的一条边,R 类似。
code
#include<bits/stdc++.h>
using namespace std;
int preg, prer, preb, fr, lr, fb, lb, mxr, mxb;
int main() {
int n;
cin >> n;
long long ans = 0;
for (int i = 0; i < n; i++) {
int p;
char c[2];
scanf("%d%s", &p, c);
if(c[0] == 'G') {
if(prer) mxr = max(mxr, p - prer);
if(preb) mxb = max(mxb, p - preb);
if(!preg) { // 第一个 G 前面
if(fr) {
ans += p - fr;
}
if(fb) {
ans += p - fb;
}
} else { // 两个 G 之间
ans += min(2LL * (p - preg), 3LL * (p - preg) - mxb - mxr);
}
prer = p;
preb = p;
preg = p;
lb = lr = p;
mxb = mxr = 0;
} else if(c[0] == 'B') {
if(!fb) fb = p;
if(preb) mxb = max(mxb, p - preb);
preb = p;
lb = p;
} else if(c[0] == 'R') {
if(!fr) fr = p;
if(prer) mxr = max(mxr, p - prer);
prer = p;
lr = p;
}
}
if(!preg) {
if(lr != fr) ans += lr - fr;
if(lb != fb) ans += lb - fb;
} else {
ans += lr - preg;
ans += lb - preg;
}
cout << ans << endl;
return 0;
}
G. New Year and Original Order
分析
(dp[i][j][k][o]) 表示前 (i) 个数中有 (j) 个大于等于 (k) 的数时的构数方案数,(o) 为了保证边界,构造的数不大于 (X)。
举个例子,比方说一个数 (3312) 我们要累积这个数对答案的贡献,实际上是 (1233) ,大于等于 (1) 的数有 (4) 个,我们可以认为贡献了 (1111) ,大于等于 (2) 的数有 (3) 个,贡献了 (111) ,大于等于 (3) 的数有 (2) 个,贡献了 (11) 。
最后枚举 (k),累计计算一下答案即可。
code
#include<bits/stdc++.h>
using namespace std;
const int MOD = 1e9 + 7;
const int N = 707;
char s[N];
int dp[N][N][10][2];
int main() {
scanf("%s", s + 1);
int n = strlen(s + 1);
for (int i = 1; i < 10; i++) {
dp[0][0][i][0] = 1;
}
for (int i = 0; i < n; i++) {
for (int j = 0; j <= i; j++) {
for (int k = 1; k < 10; k++) {
for (int o = 0; o < 2; o++) {
for (int p = 0; p <= (!o ? s[i + 1] - '0' : 9); p++) {
(dp[i + 1][j + (p >= k)][k][o | p < s[i + 1] - '0'] += dp[i][j][k][o]) %= MOD;
}
}
}
}
}
long long ans = 0;
for(int k = 1; k < 10; k++) {
for (long long i = 1, p = 1; i <= n; i++, p = (p * 10 + 1) % MOD) {
(ans += p * (dp[n][i][k][0] + dp[n][i][k][1]) % MOD) %= MOD;
}
}
printf("%I64d
", ans);
return 0;
}