2016 Multi-University Training Contest 7 1002
hdu5794 A Simple Chess
题意:有一个棋盘,上面有一个棋子,棋子走法和象棋中的马一样,且只能向右下方走。棋子一开始在左上角(1,1)要使求棋子走到右下角(n,m)。棋盘上有r个障碍点不能通过(不考虑蹩马腿),问棋子走到右下角的方法数取模后答案。
在赛场上想的是lucas定理+容斥,看了看数据感觉过不了没敢写,赛后知道果然还是煞笔数据,把不可达点去掉后完全可以O(2^n)容斥(官方题解说的)。
补题的时候参考的别人的dp写法。
具体解法:
把每个点化成以(0,0)为起始的点,方便处理。找规律可以发现如果没有障碍物的话,到达某点的方法数是一个组合数,可达点必须满足(x+y)%3 == 0,且x >= (x+y)/3 && y >= (x+y)/3。凑数(…)可得组合数中较大数是(x+y)/3,较小数是x-(x+y)/3。组合数求模用lucas定理来求。lucas的板用的阳神的,膜阳神。
然后就是dp了。dp[i]表示达到第i个点的方法数。则第i点的方法数为:从起点直接到该点方法数-sum(前面障碍点到达该点方法数 * 起点到前面障碍点的方法数),即dp[i] = dp[i] - sum(dp[j] * C(a[i].x - a[j].x , a[i].y - a[j].y). 要求a[i].x >=a[j].x && a[i].y >= a[j].y。
处理dp值之前要先把障碍点排序一下。最理想的方法是按点的层数排序,但是根据选点的要求可以直接按x坐标排个序。
可以把终点作为最后一个障碍点,最后答案就是终点所对应的dp值。
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <list>
#include <set>
#include <stack>
#include <queue>
#include <deque>
#include <algorithm>
#include <functional>
#include <numeric>
#include <iomanip>
#include <climits>
#include <new>
#include <utility>
#include <iterator>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cmath>
#include <ctime>
using namespace std;
typedef long long LL;
const LL N = 110119;
LL factorial[120500];
LL p = 110119;
LL dp[2010];
struct node
{
LL x,y;
bool operator < (const node & nn) const
{
return x < nn.x;
}
};
node a[2010];
void init()
{
factorial[0] = 1;
for (LL i = 1; i <= 120000; ++i)
factorial[i] = factorial[i-1] * i % p;
}
LL p_pow(LL a, LL n, LL p)
{
LL ret = 1ll;
LL A = a;
while (n)
{
if (n & 1ll)
ret = ret * A % p;
A = (A * A) % p;
n >>= 1ll;
}
return ret;
}
LL Lucas(LL a, LL k, LL p)
{
if(a < 0 || k < 0)
return 0ll;
LL re = 1ll;
while (a != 0ll && k != 0ll)
{
LL aa = a % p, bb = k % p;
if (aa < bb)
return 0;
re = re * factorial[aa] * p_pow(factorial[bb]*factorial[aa-bb]%p, p-2, p) % p;
a /= p;
k /= p;
}
return re;
}
bool judge(LL x, LL y)
{
if(x < 0 || y < 0 || (x + y) %3 != 0)
return false;
LL t = (x+y)/3;
if(x < t && y < t)
return false;
return true;
}
int main()
{
init();
LL n,m,r,x,y,cas = 1;
bool flag = false;
while(~scanf("%I64d%I64d%I64d", &n, &m, &r))
{
memset(dp,0,sizeof(dp));
memset(a,0,sizeof(a));
flag = false;
for(LL i = 0; i < r; ++i)
{
scanf("%I64d%I64d", &x, &y);
if(x == n && y == m)
flag = true;
a[i].x = x-1,a[i].y = y-1;
}
if(flag == true || !judge(n-1,m-1))
{
printf("Case #%I64d: 0\n",cas++);
continue;
}
sort(a,a+r);
a[r].x = n-1,a[r].y = m-1;
++r;
for(LL i = 0; i < r; ++i)
{
if(judge(a[i].x,a[i].y))
{
LL nn,mm;
nn = (a[i].x + a[i].y)/3;
mm = min(a[i].x, a[i].y) - nn;
dp[i] = Lucas(nn,mm,p);
for(LL j = 0; j < i; ++j)
{
if(a[i].x >= a[j].x && a[i].y >= a[j].y)
{
LL xx = a[i].x - a[j].x, yy = a[i].y - a[j].y;
if(judge(xx, yy))
{
LL dd = (xx + yy) / 3;
LL gg = min(xx,yy) - dd;
dp[i] = (dp[i] - (dp[j] * Lucas(dd,gg,p) % p) + p) % p;
}
}
}
}
}
printf("Case #%I64d: %I64d\n",cas++,dp[r-1]);
}
return 0;
}