Atcoder Regular Contest 058 题解
ARC058C. Iroha's Obsession *1174
\(n\) 再大一点的就是巨大恶心分类讨论。但我们注意到 \(n \leq 10^4\),所以我们可以直接暴力枚举然后写个 check。首先我们先把被 ban 掉的数存标记一下。然后从 \(n\) 开始往上查,一直查到 \(10^6\) 基本就可以了。然后每次检查一下有没有数位被 ban 掉,没有的话直接输出然后退出就好了。
#include <bits/stdc++.h>
using namespace std;
int n, k, x;
bool D[11];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> k;
for(int i = 1; i <= k; i ++)
{
cin >> x;
D[x] = true;
}
for(int i = n; i <= 1000000; i ++)
{
int ii = i;
bool Flag = true;
while(ii > 0)
{
if(D[ii % 10]) Flag = false;
ii /= 10;
}
if(Flag)
{
cout << i << '\n';
break;
}
}
return 0;
}
ARC058D. Iroha and a Grid *1905
还算比较经典的组合数学题目。我们注意到 \(n,m\) 非常大,不足以让我们动态规划。但是我们注意到它只 ban 掉一个矩形,于是我们可以考虑分阶段组合计数。
我们先考虑如果没有任何矩形被 ban 掉我们要怎么做。这是简单的,就是 \(\binom{n+m-2}{n-1}\),或者是 \(\binom{n+m-2}{m-1}\),因为由组合恒等式,这两个东西是相等的。
接下来我们考虑它 ban 掉了哪个区域,其实就是 \((n-A+1,1)\) 到 \((n,B)\) 这段区域。那我们考虑把矩阵切了,变为 \((1,1)\) 到 \((n-A,B)\) 和 \((1,B+1)\) 到 \((n,n)\) 这两部分。然后枚举分界点,用乘法原理和加法原理统计它们对于答案的贡献。其实就是
然后就做完了。因为我们需要计算组合数,所以我们可能还需要预处理一步阶乘和阶乘逆元。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MAXN = 200005;
const LL MOD = 1e9 + 7;
LL n, m, A, B, fac[MAXN], inv[MAXN];
LL QuickPow(LL a, LL b, LL p)
{
LL res = 1;
while(b > 0)
{
if(b & 1) res = (res * a) % p;
a = (a * a) % p;
b >>= 1;
}
return res;
}
LL C(LL a, LL b)
{
if(a < b) return 0;
else return ((fac[a] * inv[b]) % MOD * inv[a - b]) % MOD;
}
LL calc(LL a, LL b) { return C(a + b - 2, a - 1); }
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m >> A >> B;
fac[0] = 1;
for(LL i = 1; i <= n + m - 2; i ++) fac[i] = (fac[i - 1] * i) % MOD;
inv[n + m - 2] = QuickPow(fac[n + m - 2], MOD - 2, MOD);
for(LL i = n + m - 3; i >= 0; i --) inv[i] = (inv[i + 1] * (i + 1)) % MOD;
LL ans = 0;
for(LL i = 1; i <= n - A; i ++)
ans = (ans + (calc(i, B) * calc(n - i + 1, m - B)) % MOD) % MOD;
cout << ans << '\n';
return 0;
}
ARC058E. Iroha and Haiku *2473
过于神秘了,一眼丁真为我做不出来的题。
首先我们肯定知道如果正着做肯定是不好做的,一个很直接的原因就是贡献会算重复。然后就寄了。所以我们算有多少个序列是不好的。
然后我们考虑到它的 \(x,y,z\) 特别小,所以其实我们非常容易联想到状态压缩动态规划。事实证明我猜对了,但是我会不了一点。我们发现不仅 \(x,y,z\) 小,\(x+y+z\) 也在可接受范围内。于是我们考虑有没有关于 \(x+y+z\) 这个常数的时间复杂度。
我们定义 \(f_{i,j}\) 为考虑到第 \(i\) 个位置,其后缀和存在状态为 \(j\) 的方案数。 就非常神奇的一个状态定义。因为我们考虑到我们关心的后缀和情况最多只有 \(17\) 位,所以我们考虑设计这个状态。
什么情况下这个后缀和串是不好的呢?非常简单,有三种情况:
- 不存在一个后缀和等于 \(z\)
- 不存在一个后缀和等于 \(y+z\)
- 不存在一个后缀和等于 \(x+y+z\)
至于怎么计算后缀和串,这是简单的。我们考虑枚举 \(k\) 从 \(1\) 到 \(10\),那么加上这位后缀和就从 \(j\) 变为了 ((j << k) | (1 << (k - 1))) & ((1 << (X + Y + Z)) - 1)。这是容易理解的。然后就是普通的计数,后面是简单的。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MOD = 1e9 + 7;
LL n, X, Y, Z, tot = 1, DP[45][(1 << 17) + 5];
bool check(LL stat)
{
if(!(stat & (1 << (Z - 1)))) return true;
if(!(stat & (1 << (Y + Z - 1)))) return true;
if(!(stat & (1 << (X + Y + Z - 1)))) return true;
return false;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> X >> Y >> Z;
for(LL i = 1; i <= n; i ++) tot = (tot * 10) % MOD;
DP[0][0] = 1;
for(LL i = 1; i <= n; i ++)
{
for(LL j = 0; j < (1 << (X + Y + Z)); j ++)
{
for(LL k = 1; k <= 10; k ++)
{
LL stat = ((j << k) | (1 << (k - 1))) & ((1 << (X + Y + Z)) - 1);
if(!check(stat)) continue;
DP[i][stat] = (DP[i][stat] + DP[i - 1][j]) % MOD;
}
}
}
for(LL i = 0; i < (1 << (X + Y + Z)); i ++) tot = ((tot - DP[n][i]) % MOD + MOD) % MOD;
cout << tot << '\n';
return 0;
}
F 题是巨大不可做题,我没有能力补。

浙公网安备 33010602011771号