Atcoder ARC 059 题解
D - Iroha and a Grid
题意
给出一个 \(n \times m \ (1 \le n,m \le 100,000)\) 的地图,只能从下或者从右走。有一片区域禁止走入,也就是禁止走入左下角的 \(a \times b\) 的方格,求从 \((1, 1)\) 走到 \((n, m)\) 的方案数。
思路
看了一看,排列组合可以计算方格图的方案数,然后我们可以把这个地图分成两部分。中间被ban掉的地图的右边框就是一列分界线,枚举一下分界线上的点,计算从 \((1, 1)\) 到该点的方案数乘从该点到 \((n, m)\) 方案数,最后加和起来就行了。
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 200005;
const int mod = 1000000007;
ll fac[N], inv[N];
ll ksm(ll a, ll b)
{
ll res = 1;
while(b)
{
if(b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
void init()
{
fac[0] = inv[0] = 1ll;
for(int i = 1; i < N; i ++)
fac[i] = 1ll * fac[i - 1] * i % mod;
inv[N - 1] = ksm(fac[N - 1], mod - 2);
for(int i = N - 2; i >= 1; i --)
inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
}
ll C(ll a, ll b)
{
return fac[a] * inv[b] % mod * inv[a - b] % mod;
}
signed main()
{
init();
ll n, m, a, b;
cin >> n >> m >> a >> b;
ll res = 0;
for(int i = 1; i <= n - a; i ++)
{
ll t1 = C(b + i - 2, b - 1);
ll t2 = C(n + m - b - i - 1, m - b - 1);
res = (res + t1 * t2 % mod) % mod;
}
cout << res << '\n';
}
E - Iroha and Haiku
题意
定义一个合法序列为:在长度为 \(n\) 的序列中,序列中的每一个数字都是 \([0,10]\) ,有一段连续和可以被分成三段,三段的和分别为 \(x, \ y, \ z\)。给定 \(n, \ x, \ y, \ z \ (1 \le n \le 40, \ 1 \le x, z \le 5, \ 1 \le y \le 7)\)。请问合法序列的方案数是多少。
思路
发现正面去思考会很难去重,所以考虑取补集。因为数字都很小,又是个计数问题,于是考虑 \(DP\) 来做。我们肯定是要枚举每一位的数字,所以这是第一个维度;然后第二个维度的话,需要“状压”来记录一下状态。这里的状压和通常的二进制状压不一样。例如:1 - 1;2 - 10, 3 - 100。可以发现这里的状态表示的就是长度为数字的01串,且最高位为1。为什么是这样设计呢?因为3 + 2 = 5,那么10010也是包含着10000的,那么我们就可以用这个状态来表示和了。我们枚举一下序列第 \(i\) 位,枚举状态,枚举这位填入的数字,若不合法的话就累加一下方案数,最后用 \(10^n-\sum_{i=0}^{S} f[n][i]\) 就是答案了。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 42, mod = 1000000007;
ll n, x, y, z;
ll f[N][1 << 17];
int main()
{
cin >> n >> x >> y >> z;
ll res = 1;
for(int i = 1; i <= n; i ++)
res = res * 10 % mod;
ll vaild_st = (1 << (x - 1)) | (1 << (x + y - 1)) | (1 << (x + y + z - 1));
ll st = (1 << (x + y + z)) - 1;
f[0][0] = 1;
for(int i = 1; i <= n; i ++)
{
for(ll j = 0; j <= st; j ++)
{
for(ll k = 1; k <= 10; k ++)
{
ll t = (j << k) | (1 << (k - 1)); //在当前状态后面加上填入的数字
t &= st; //只取最后(x + y + z)位
if((t & vaild_st) != vaild_st)
f[i][t] += f[i - 1][j], f[i][t] %= mod;
}
}
}
for(int i = 0; i <= st; i ++)
res = (res - f[n][i] + mod) % mod;
cout << res << '\n';
}