AcWing 1214. 波动数列 -动态规划

题意分析

对于本题我们可以列出下列等式:
x + ( x + d 1 ) + ( x + d 1 + d 2 ) + . . . + ( x + d 1 + d 2 + . . . + d n − 1 ) = s x+(x+d_1)+(x+d_1+d_2)+...+(x+d_1+d_2+...+d_{n-1})=s x+(x+d1)+(x+d1+d2)+...+(x+d1+d2+...+dn1)=s
 其中, d = { + a , − b } d=\{ +a,-b\} d={+a,b}

 变形得:
n x + ( n − 1 ) d 1 + ( n − 2 ) d 2 + . . . + d n − 1 = s nx+(n-1)d_1+(n-2)d_2+...+d_{n-1}=s nx+(n1)d1+(n2)d2+...+dn1=s
 对于一个式子中有两个变量 x , d i x,d_i x,di,利用式子可以转换:
x = s − ( ( n − 1 ) d 1 + ( n − 2 ) d 2 + . . . + d n − 1 ) n x=\frac{s-((n-1)d_1+(n-2)d_2+...+d_{n-1})}{n} x=ns((n1)d1+(n2)d2+...+dn1)
 根据上式,我们可以得出信息:对于一定的n和s,一组 d i d_i di对应着确定 x x x,有多少组符合条件的 d i d_i di就是题目的总方案数,所以该问题可以转换成组合问题,即 d 1 . . . d i d_1...d_i d1...di的组合情况,每个 d d d有两种情况,取+a或者-b

 注意,题目有对 x x x的约束,即 x x x必须为整数,所以要求 s − ( ( n − 1 ) d 1 + ( n − 2 ) d 2 + . . . + d n − 1 ) s-((n-1)d_1+(n-2)d_2+...+d_{n-1}) s((n1)d1+(n2)d2+...+dn1)必须是 n n n的倍数,即 ( n − 1 ) d 1 + ( n − 2 ) d 2 + . . . + d n − 1 (n-1)d_1+(n-2)d_2+...+d_{n-1} (n1)d1+(n2)d2+...+dn1 s s s n n n取模的值要相等。

 所以,根据闫氏DP分析法,我们进行下述假设:

  • 状态表示(确定集合) f ( i , j ) f(i,j) f(i,j)表示在前 i i i项,余数为 j j j的组合数,最终的结果就是 f ( n − 1 , s % n ) f(n-1,s\%n) f(n1,s%n)
  • 状态计算(子集划分) f ( i , j ) f(i,j) f(i,j)的所有情况可以划分为第 i i i项的 d d d + a +a +a,和第 i i i项的 d d d − b -b b,两种情况,而这两种情况又分别对应 f ( i − 1 , ( j − ( n − i ) ∗ a ) % n ) f(i-1,(j-(n-i)*a)\%n) f(i1,(j(ni)a)%n) f ( i − 1 , ( j + ( n − i ) ∗ b ) % n ) f(i-1,(j+(n-i)*b)\%n) f(i1,(j+(ni)b)%n)
    f ( i , j ) = f ( i − 1 , ( j − i ∗ a ) % n ) + f ( i − 1 , ( j + i ∗ b ) % n ) f(i,j)=f(i-1,(j-i*a)\%n)+f(i-1,(j+i*b)\%n) f(i,j)=f(i1,(jia)%n)+f(i1,(j+ib)%n)

    Q:为什么前 i − 1 i-1 i1项的和的余数是 ( j − ( n − i ) ∗ a ) % n (j-(n-i)*a)\%n (j(ni)a)%n呢?
    A:假设前 i − 1 i-1 i1项的和为 C C C,那么就有 ( C + ( n − i ) ∗ a ) % n = j (C+(n-i)*a)\%n=j (C+(ni)a)%n=j,即 C % n = ( j − ( n − i ) ∗ a ) % n C\%n=(j-(n-i)*a)\%n C%n=(j(ni)a)%n
    至此,所有分析就已经结束,可以直接写代码了,其他细节在代码中完善。

AC CODE

#include <iostream>
using namespace std;
const int N=1005;
#define p 100000007

int f[N][N];

//获取a除以b的正的正余数
int get_mod(int a,int b)
{
    return (a%b+b)%b;
}
int main()
{
    int n,s,a,b;
    cin>>n>>s>>a>>b;
    
    //初始化,长度为0,余数也为0的情况方案为1
    f[0][0]=1;
    
    for(int i=1; i<n; i++)
        for(int j=0; j<n; j++)
            f[i][j] = (f[i-1][get_mod(j-(n-i)*a,n)] + f[i-1][get_mod(j+(n-i)*b,n)])%p;
    cout<<f[n-1][get_mod(s,n)];
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

linengcs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值