zoukankan      html  css  js  c++  java
  • AGC013D题解

    做题思路

    简单想法

    根据题意可以得出显而易见的结论:

    在每次操作完成后,白球有 (x) 个时,黑球数量为 (n-x) 。于是我们设白球数量为 (x) 个。

    对于本题的操作,仅有以下四种:

    条件 (xge 1) :

    • 取出一个白球,加入一对黑白球,取出一个白球
    • 取出一个白球,加入一对黑白球,取出一个黑球

    条件 (x<n) :

    • 取出一个黑球,加入一对黑白球,取出一个白球
    • 取出一个黑球,加入一对黑白球,取出一个黑球

    那么我们可以想到一个简单的 DP :

    (f_{i,j}) 为进行到操作 i 还剩 j 个白球的操作方案数,初始状态为

    (f_{0,j}=1 , jin[0,n])

    根据题意我们可以得到状态转移方程:

    if(j>=1)
    	f[i+1][j-1]=f[i+1][j-1]+f[i][j],
    	f[i+1][j]=f[i+1][j]+f[i][j];
    if(j<n)
    	f[i+1][j]=f[i+1][j]+f[i][j],
    	f[i+1][j+1]=f[i+1][j+1]+f[i][j];
    

    但是这样转移显然有问题,因为当初始时白球数量不同时,进行相同的操作会得到相同的序列,而这种方案被我们进行了重复计算。

    完善想法

    那么问题变为了如何去除同一方案的重复贡献。

    为了方便,我们将每次操作前后白球的数量变化画为折线,那么上文提到的操作可以用折线所表示:

    那么假设我们对某种初始情况进行了操作 114314 (假设初始时白球数量为5),我们得到了以下折线:

    显然,初始白球数量不同(折线起点纵坐标不同)且操作合法(操作期间白球数量(num_{white}in [0,n])) 我们在 DP 时发生了重复统计,反映到折线上(假设 (n=6) )就如下图所示:

    每一条折线所产生的颜色序列完全相同,但显然被统计了多次。

    于是,我们产生了一个想法,对于每一种折线,只标记其中的一条,只统计它自己的贡献就行了。那么标记哪一条折线比较方便呢?

    继续看上面的图,可以发现这些形状相同的折线唯一的不同点就是值域不同,于是就产生了一个做法: 我们只统计那些触碰到边界(与x轴接触的到折线)。

    因为对于每一种形态且合法的折线,由于不同的起始点,所经历的纵坐标值域不同,仅有一条触碰到了x轴,其他折线均可以这条折线向上平移若干单位得到。

    那么,我们可以将 DP 数组 (f_{i,j}) 多开一维变成 (f_{i,j,k}) ,(k)这一维表示这条折线是否接触到x轴,最后我们只需要统计 (sum^{n}_{j=0}f_{m,j,1}) 就行了

    初始状态为 (f_{0,j,0}=1,jin [0,n] and f_{0,0,1}=1).

    还有一点要注意的就是 j=1 时进行操作二时,折线其实是会接触到x轴的(第一步拿走一个白球会使白球数为0)

    那么我们就能 偷税地 DP 了,

    Code

    #include<cmath>
    #include<cstdio>
    #include<vector>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define N 3005
    #define LL long long 
    using namespace std;
    
    int n,m,f[N][N][2],ans;
    const int mod = 1000000007;
    
    inline int qr()
    {
        int x=0,w=1;char ch=0;
        while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
        return x*w;
    }
    
    inline int add(int a,int b){return a+b>mod?a+b-mod:a+b;}//让加法快一点(可能)
    
    int main()
    {
    	n=qr(),m=qr();
    	f[0][0][1]=1;   //初始化部份
    	for(register int j=1;j<=n;j++) f[0][j][0]=1;
    	for(register int i=0;i<m;i++)        //向后推操作
    		for(register int j=0;j<=n;j++)   //枚举剩下多少个白球
    		{
    			if(j-1>=0)
    			{
    				//如果j==1时进行操作一和二会使折线接触到x轴(白球数变成0)
    				j==1 ? f[i+1][j-1][1]=add(f[i+1][j-1][1],f[i][j][0]) : f[i+1][j-1][0]=add(f[i+1][j-1][0],f[i][j][0]);//操作一
    				f[i+1][j-1][1]=add(f[i+1][j-1][1],f[i][j][1]);
    				j==1 ? f[i+1][j][1]=add(f[i+1][j][1],f[i][j][0]) : f[i+1][j][0]=add(f[i+1][j][0],f[i][j][0]);//操作二
    				f[i+1][j][1]=add(f[i+1][j][1],f[i][j][1]);
    			}
    			if(j+1<=n)
    			{
    				f[i+1][j+1][1]=add(f[i+1][j+1][1],f[i][j][1]);//操作三
    				f[i+1][j+1][0]=add(f[i+1][j+1][0],f[i][j][0]);
    				f[i+1][j][1]=add(f[i+1][j][1],f[i][j][1]);//操作四
    				f[i+1][j][0]=add(f[i+1][j][0],f[i][j][0]);
    			}
    		}
    	for(register int j=0;j<=n;j++)//累加答案
    		ans=add(ans,f[m][j][1]);
    	printf("%d
    ",ans);
        return 0;
    }
    
  • 相关阅读:
    箭头函数中的this
    剑指offer(十六) 合并两个排序的链表
    http中的referer
    剑指offer(十四,十五)链表中倒数第k个结点,反转链表
    剑指offer(十二,十三) 数值的整数次方,调整数组顺序使奇数位于偶数前面
    那些短小精悍的&奇葩的&令人感到惊讶的JavaScript代码----更新中
    对箭头函数的补充
    Promise和Async/Await用法整理
    Vue父子组件互相通信实例
    Vue实例里面的data属性为什么用函数返回
  • 原文地址:https://www.cnblogs.com/isonder/p/14613803.html
Copyright © 2011-2022 走看看