CodeForces - 560E Gerald and Giant Chess(组合数学+dp)

题目链接:点击查看

题目大意:给出一个 n ∗ m n*m nm 的矩阵,其中有 k k k 个坏点,每次只能向右走或向下走,问从点 ( 1 , 1 ) (1,1) (1,1) 到点 ( n , m ) (n,m) (n,m) 共有多少种不同的路线

题目分析:刷知乎看到的一道题,心血来潮就想写题了

数据范围 n , m n,m n,m 都是 1 e 5 1e5 1e5 级别的,而 k k k 却只有 2000 2000 2000,所以从坏点入手,考虑 d p dp dp 和组合数学

首先对于两个点 ( x 1 , y 1 ) (x1,y1) (x1,y1) ( x 2 , y 2 ) (x2,y2) (x2,y2) 来说,如果没有坏点的限制,那么这两个点之间的路线有 C ( x 2 − x 1 ) + ( y 2 − y 1 ) x 2 − x 1 C_{(x2-x1)+(y2-y1)}^{x2-x1} C(x2x1)+(y2y1)x2x1 条,下面可以分两步进行思考,对于每个坏点 ( x , y ) (x,y) (x,y) 来说:

  1. 加上点 ( 1 , 1 ) (1,1) (1,1) 到点 ( x , y ) (x,y) (x,y) 之间方案数
  2. 对于每个满足 x x < = x xx<=x xx<=x y y < = y yy<=y yy<=y 的坏点 ( x x , y y ) (xx,yy) (xx,yy) 来说,点 ( 1 , 1 ) (1,1) (1,1) 经过点 ( x x , y y ) (xx,yy) (xx,yy) 然后到达点 ( x , y ) (x,y) (x,y) 的这些路线显然是不合法的,减去即可

综上所述,将所有坏点排序之后的转移方程就是(设 f ( x 1 , y 1 ) ( x 2 , y 2 ) f_{(x1,y1)}^{(x2,y2)} f(x1,y1)(x2,y2) 为坏点 ( x 1 , y 1 ) (x1,y1) (x1,y1) 到坏点 ( x 2 , y 2 ) (x2,y2) (x2,y2) 的方案数, d p i dp_i dpi 为点 ( 1 , 1 ) (1,1) (1,1) 到第 i i i 个坏点的方案数):
d p i = f ( 1 , 1 ) ( p i . x , p i . y ) − [ p j . x < = p i . x   & &   p j . y < = p i . y ] ∗ d p j ∗ f ( p i . x , p i . y ) ( p j . x , p j . y ) dp_i=f_{(1,1)}^{(p_i.x,p_i.y)} \\ -[p_j.x<=p_i.x\ \&\&\ p_j.y<=p_i.y]*dp_j*f_{(p_i.x,p_i.y)}^{(p_j.x,p_j.y)} dpi=f(1,1)(pi.x,pi.y)[pj.x<=pi.x && pj.y<=pi.y]dpjf(pi.x,pi.y)(pj.x,pj.y)
将点 ( n , m ) (n,m) (n,m) 设为第 k + 1 k+1 k+1 个坏点,那么答案自然就是 d p k + 1 dp_{k+1} dpk+1

代码:

// #pragma GCC optimize(2)
// #pragma GCC optimize("Ofast","inline","-ffast-math")
// #pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
template<typename T>
inline void read(T &x)
{
     
	T f=1;x=0;
	char ch=getchar();
	while(0==isdigit(ch)){
     if(ch=='-')f=-1;ch=getchar();}
	while(0!=isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	x*=f;
}
template<typename T>
inline void write(T x)
{
     
	if(x<0){
     x=~(x-1);putchar('-');}
    if(x>9)write(x/10);
    putchar(x%10+'0');
}
const int inf=0x3f3f3f3f;
const int N=1e6+100;
const int mod=1e9+7;
struct Point {
     
	int x,y;
	void input() {
     
		read(x),read(y);
	}
	bool operator<(const Point& t)const {
     
		if(x!=t.x) {
     
			return x<t.x;
		}
		return y<t.y;
	}
}p[N];
LL dp[N],fac[N],inv[N];
LL q_pow(LL a,LL b) {
     
	LL ans=1;
	while(b) {
     
		if(b&1) {
     
			ans=ans*a%mod;
		}
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}
LL C(int n,int m) {
     
	if(n<m) {
     
		return 0;
	}
	return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
void init() {
     
	fac[0]=1;
	for(int i=1;i<N;i++) {
     
		fac[i]=fac[i-1]*i%mod;
	}
	inv[N-1]=q_pow(fac[N-1],mod-2);
	for(int i=N-2;i>=0;i--) {
     
		inv[i]=inv[i+1]*(i+1)%mod;
	}
}
int main()
{
     
#ifndef ONLINE_JUDGE
//  freopen("data.in.txt","r",stdin);
//  freopen("data.out.txt","w",stdout);
#endif
//  ios::sync_with_stdio(false);
	init();
	int n,m,k;
	read(n),read(m),read(k);
	for(int i=1;i<=k;i++) {
     
		p[i].input();
	}
	p[++k]={
     n,m};
	sort(p+1,p+1+k);
	for(int i=1;i<=k;i++) {
     
		dp[i]=C((p[i].x-1)+(p[i].y-1),(p[i].x-1));
		for(int j=1;j<i;j++) {
     
			if(p[i].x>=p[j].x&&p[i].y>=p[j].y) {
     
				dp[i]=(dp[i]-dp[j]*C((p[i].x-p[j].x)+(p[i].y-p[j].y),p[i].x-p[j].x)%mod+mod)%mod;
			}
		}
	}
	write(dp[k]);
    return 0;
}

你可能感兴趣的:(组合数学,动态规划,CodeForces上分)