zoukankan      html  css  js  c++  java
  • P2051 [AHOI2009]中国象棋

    题目描述

    这次小可可想解决的难题和中国象棋有关,在一个 n 行 m 列的棋盘上,让你放若干个炮(可以是 0个),使得没有一个炮可以攻击到另一个炮,

    请问有多少种放置方法。大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,

    且它们之间恰好 有一个棋子。你也来和小可可一起锻炼一下思维吧!

    输入格式

    一行包含两个整数 n,m之间由一个空格隔开。

    输出格式

    总共的方案数,由于该值可能很大,只需给出方案数模 9999973 的结果。

    输入输出样例

    输入 #1

    1 3
    输出 #1

    7

    说明/提示

    样例说明

    除了 3 个格子里都塞满了炮以外,其它方案都是可行的,所以一共有 2×2×2−1=7 种方案。

    数据规模与约定

    对于 30%的数据,n 和 m 均不超过 6。
    对于 50% 的数据,n 和 m 至少有一个数不超过 8。
    对于 100% 的数据,1≤n,m≤100。

    对于前30%的数据,我们可以爆搜拿部分暴力分。

    然后,我们就需要开始考虑正解。

    首先,根据中国象棋的芝士,每行每列只能放两个炮(这不是废话吗)。

    我们就可以根据这个列出方程

    我们设 f[i][j][k]表示第i行,其中有j列放了一个棋子,k列放了两个棋子的方案数。

    转移:考虑第i行的放置情况

    1. 不放的情况 这时候我们可以直接由f[i-1][j][k]转移过来

    2. 放一个的情况,并且放在了之前没有炮的列上,由于我们在没有炮的一列放了一个,会使放一个炮的列数加1,

      这种没放炮的列一共有\(m-j-k+1\)列,统计一下就行了

    3. 放一个炮,并且放在了之前就有一个炮的列上,我们在一个有一个炮的列上放了一个,就会导致放两个炮的列加一.

      放一个炮列的减一,再加上有j-1列可以放。就是 f[i-1][j+1][k-1] * (j+1)

    4. 放两个炮的情况,两个炮都放在没有炮的列上,会导致放一个炮的列加二,并且这样的列一共有\(m-j-k+2\)

      利用组合数算出答案就解决了方程就是 f[i-1][j-2][k] * calc(m-j-k+2,2)

    5. 放两个炮的情况,都放在之前放过两个炮的列上,会导致放两个炮的列加二,放一个炮的列数减二,再加上这样的列

      一共有\(j+2\)列。 方程就可以写成 f[i-1][j+2][k-2] * calc(j+2,2)

    6. 放两个炮的情况,且一个放在之前没有炮的地方,另一个放在之前有一个炮的地方,这就会导致放两个炮的数量加一。

      放一个炮的列数不变(加一后又减一) 再加上有\(j*(k-1)\)中情况,直接计算就okk了

    答案就是\(\sum_{i=0}^{m} \sum_{j=0}^{i+j<=m}f[n][i][j]\)

    这题,分类讨论确实烦了点,令人想吐

    代码

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    #define LL long long 
    const int p = 9999973;
    int n,m;
    LL c[110][110],f[110][110][110],ans;
    void YYCH()
    {
    	c[0][0] = 1;
    	for(int i = 1; i <= 100; i++)//杨辉三角求组合数
    	{
    		c[i][0] = c[i][i] = 1;
    		for(int j = 0; j <= i; j++)
    		{
    			c[i][j] = (c[i-1][j] + c[i-1][j-1]) % p;
    		}
    	}
    }
    LL calc(int n,int m)
    {
    	return c[n][m];
    }
    int main()
    {
    	scanf("%d%d",&n,&m); YYCH();
    	f[0][0][0] = 1;
    	for(int i = 1; i <= n; i++)
    	{
    		for(int j = 0; j <= m; j++)
    		{
    			for(int k = 0; k+j <= m; k++)//分类讨论
    			{
    				f[i][j][k] = (f[i][j][k] + f[i-1][j][k]) % p;//不放 
    				if(j-1 >= 0) f[i][j][k] = (f[i][j][k] + f[i-1][j-1][k] * (m-j-k+1) % p) % p;//放一个棋子在没有棋子的列中 
    				if(k-1 >= 0 && j+1 <= m) f[i][j][k] = (f[i][j][k] + f[i-1][j+1][k-1] * (j+1) % p) % p;//放一个棋子在之前有一个棋子的列中 
    				if(j-2 >= 0) f[i][j][k] = (f[i][j][k] + f[i-1][j-2][k] * calc(m-j-k+2,2)) % p;//放两个棋子在之前都没有放的列中 
    				if(k-2 >= 0 && j+2 <= m) f[i][j][k] = (f[i][j][k] + f[i-1][j+2][k-2] * calc(j+2,2) % p) % p; //放两个棋子在之前放过一个棋子的列中 
                                    if(j-1 >= 0 && k-1 >= 0) f[i][j][k] = (f[i][j][k] + f[i-1][j][k-1] * (j) * (m-j-k+1) % p) % p; //一个放在之前有一个的列中一个放在之前没有棋子的列中 
    			}
    		}
    	}
    	for(int i = 0; i <= m; i++)
    	{
    		for(int j = 0; j+i <= m; j++) 
    		{
    			ans = (ans + f[n][i][j]) % p;
    		}
    	}
    	printf("%lld\n",ans);
    	return 0;
    }
    
    

    ENDING

  • 相关阅读:
    Android应用开发SharedPreferences存储数据的使用方法
    Android ListView不响应OnItemClickListener解决办法
    Java 毫秒转换为日期类型、日期转换为毫秒
    关于android软键盘enter键的替换与事件监听
    如何使用adb命令查看android中的数据库
    android坐标
    getHitRect获取点击控件的位置
    UTC的相互转换(java)
    深入理解Android的startservice和bindservice
    TS格式解析
  • 原文地址:https://www.cnblogs.com/genshy/p/13479655.html
Copyright © 2011-2022 走看看