Codeforces Round #313 (Div. 1) C. Gerald and Giant Chess

http://codeforces.com/contest/559/problem/C

Description

n×m 的网格,要从 (1,1) 走到 (n,m) ,只能沿着行或者列增加的方向走,又其中有 k 个地方不能走,问路径数目。

Data scope

1n105
1m105
1k2000

Solution

 X   S    X   X   T   X 

显然是容斥原理问题,为此我们把禁止走的格子标号。并且设 f(i1i2...ip) 表示走 i1,i2,...,ip 这些禁止格子的路径数。
 1   S    2   3   T   4 

那么:
result=f(null)f(1)f(2)f(3)f(4)+f(12)+f(13)+f(14)+f(23)+f(24)+f(34)f(123)f(124)f(134)f(234)+f(1234)

f(i1i2...ip) 的求法并不复杂,它是一系列组合数的乘积,这里不赘述。
距离解决问题的障碍是项太多。一共有 2k 项,但是应该认识到是有些项的路径是不存在的。
比如 f(14) 就是不可能的,但是 f(23) 就是可以的。原因嘛……
这就启发我们对每一个点计算一个值 dp[u] ,表示经过这个点之后要乘的组合数的和。
dp[u]=calc(u,T)vxux,vyuycalc(u,v)dp[v]

result=calc(S,T)calc(S,u)dp[u]

Evaluation

Not bad…

Code

#include <bits/stdc++.h>

const int MAXK = 2000 + 5;
const int MOD = int(1e9) + 7;

std::pair<int, int> ban[MAXK];
#define fst first
#define sec second
std::vector<int> g[MAXK];
long long dp[MAXK];
const int MAXN = int(2e5) + 5;
long long fac[MAXN];
long long inv_fac[MAXN];
int n, m, k;

void fac_table() {
  fac[0] = 1;
  for (int i = 1; i < MAXN; i++)
    fac[i] = fac[i - 1] * i % MOD;
  inv_fac[0] = 1;
  inv_fac[1] = 1;
  for (int i = 2; i < MAXN; i++)
    inv_fac[i] = (MOD - MOD / i) * inv_fac[MOD % i] % MOD;
  for (int i = 2; i < MAXN; i++)
    inv_fac[i] = inv_fac[i] * inv_fac[i - 1] % MOD;
  for (int i = 0; i < MAXN; i++)
    assert(fac[i] * inv_fac[i] % MOD == 1);
}

long long pascal(int n, int m) {
  return fac[n] * (inv_fac[m] * inv_fac[n - m] % MOD) % MOD;
}

long long calc(int x, int y, int p, int q) {
  return pascal(p + q - x - y, p - x);
}

long long calc(int u) {
  return calc(ban[u].fst, ban[u].sec, n, m);
}

long long calc(int u, int v) {
  return calc(ban[u].fst, ban[u].sec, ban[v].fst, ban[v].sec);
}

int dfs(int u) {
  if (dp[u] != -1)
    return dp[u];
  dp[u] = calc(u);
  for (int i = 0; i < g[u].size(); i++) {
    int v = g[u][i];
    dp[u] = (dp[u] + calc(u, v) * (MOD - dfs(v)) % MOD) % MOD;
  }
  return dp[u];
}

int main() {
  fac_table();
  scanf("%d %d %d", &n, &m, &k);
  for (int i = 0; i < k; i++) {
    scanf("%d %d", &ban[i].fst, &ban[i].sec);
  }
  for (int i = 0; i < k; i++) {
    for (int j = 0; j < k; j++) {
      if (i == j)
        continue;
      if (ban[j].fst >= ban[i].fst && ban[j].sec >= ban[i].sec)
        g[i].push_back(j);
    }
  }
  memset(dp, -1, sizeof(dp));
  long long res = calc(1, 1, n, m);
  for (int i = 0; i < k; i++) {
    res = (res + calc(1, 1, ban[i].fst, ban[i].sec) * (MOD - dfs(i))) % MOD;
  }
  printf("%d\n", (int) res);
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值