题意:给一个二维直角坐标系下的点(n,m)(1<=n,m<=10^18)和另外r(0<=r<=100)个点。一个棋子从(1,1)点走向(n,m),走法规定如下,设当前点为(x1,y1),棋子可以走到(x2,y2)当且仅当(x2-x1)^2+(y2-y1)^2==5且x1<x2,y1<y2。
问棋子不经过障碍点走到点(n,m)的方案数。
思路:
对于我这个蒟蒻,觉得这是一个好题。比赛的时候一个小时我们队就做出了1001和1003,接下来挂机4个小时…………(满脸黑线)
知道了思路之后其实很简单,r最大只有100,应该从这里入手,设dp[i]的含义为:
不经过其他障碍点,从(1,1)点走到i点的方案数。则dp[i]可初始化为((1,1)点到第i个点的方案数),状态转移方程dp[i]=dp[i]-(第j个点到第i个点的方案数)*dp[j],其中j 是在i之前的点。(就是把r个点按x,y排序,一个O(r^2)的二重循环)。
某点走到某点的方案数就是一个组合数,不理解的话,画图模拟下就可以很快理解。
这题还有一个问题就是数据范围太大,组合数很难计算,可以使用Lucas定理(详见代码),此法在O(mod)的预处理后,有着(以mod为底的)log级别的速度,很厉害。之前没有接触过,这次遇到了也算有收获。
自己在读入点时就去掉了很多不符合要求的点,在对点排序后,把(n,m)点放在了node数组的后面,两个for枚举点的时候,也用二维坐标系的一些知识,去掉了不符合要求的点。详见代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
#define MS(x,y) memset(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define lowbit(x) (x&(-x))
typedef long long LL;
inline void fre1(){freopen("input.txt","r",stdin);/*freopen("output.txt","w",stdout);*/}
inline void fre2(){fclose(stdin);/*fclose(stdout);*/}
const int MAXN=100+50;
const double EPS=1e-8;
const int MOD=110119;
struct Node{
LL x,y;
bool operator <(const Node& rhs) const{
return x<rhs.x||(x==rhs.x&&y<rhs.y);
}
}node[MAXN];
LL dp[MAXN],fac[MOD+5];
LL powmod(LL a,LL b){
LL ret=1;
for(;b;b>>=1){
if(b&1) ret=(ret*a)%MOD;
a=(a*a)%MOD;
}
return ret;
}
LL Lucas(LL n,LL m){
LL ret=1;
while(n&&m){
LL a=n%MOD,b=m%MOD;
if(a<b) return 0;
ret=(ret*fac[a]*powmod(fac[b]*fac[a-b]%MOD,MOD-2))%MOD;
n/=MOD;m/=MOD;
}
return ret;
}
int main()
{
fac[0]=1;
for(int i=1;i<=MOD;++i) fac[i]=(fac[i-1]*i)%MOD;
LL n,m,x,y;
int kase=0,r;
while(~scanf("%I64d%I64d%d",&n,&m,&r)){
int tot=0;
bool flag=false;
if((n+m)%3!=2) flag=true;
if(m>2*n-1) flag=true;
if(2*m<n+1) flag=true;
for(int i=0;i<r;++i){
scanf("%I64d%I64d",&x,&y);
if(x==n&&y==m) flag=true;
if(flag) continue;
if((x+y)%3!=2) continue;
if(y>2*x-1) continue;
if(2*y<x+1) continue;
node[tot++].x=x;
node[tot-1].y=y;
}
if(flag){
printf("Case #%d: 0\n",++kase);
continue;
}
sort(node,node+tot);
node[tot].x=n;node[tot].y=m;
for(int i=0;i<=tot;++i){
x=node[i].x-1,y=node[i].y-1;
LL a=(x+y)/3;
LL b=(abs(x-a)+abs(y-2*a))/2;
dp[i]=Lucas(a,b);
for(int j=0;j<i;++j){
if(2*(node[j].y-node[i].y)>node[j].x-node[i].x) continue;
if(node[j].y-node[i].y<2*(node[j].x-node[i].x)) continue;
x=node[i].x-node[j].x,y=node[i].y-node[j].y;
a=(x+y)/3;
b=(abs(x-a)+abs(y-2*a))/2;
dp[i]-=(Lucas(a,b)*dp[j])%MOD;
while(dp[i]<0) dp[i]+=MOD;
}
}
printf("Case #%d: %I64d\n",++kase,dp[tot]);
}
return 0;
}
其他:
这题看了看就有容斥的想法,但因为平时自己接触的容斥题实在是太少了,所以多校的确是一个锻炼的好机会啊。
同时,多校第一场的Rigid Frameworks,也是用到了容斥思想,而且也和这题有点类似,不断依靠之前的状态来更新自己的状态。自己写过这题:
http://blog.csdn.net/dpppbr/article/details/51972196
还有多校第似场的Lucky7,用状压进行容斥,因为此题中,每个p值a值并没有关联性,所以只能用暴力枚举的方法来计算答案,如果容斥之间的各点有关系的话,计算量就可以简化很多。