P5148 大循环

知识点:组合数学, 秦九韶算法

原题面

题目要求 :

给定下列程序:

void work()
{
	ans=0;
   for(a[1]=1;a[1]<=n;++a[1])
     for(a[2]=1;a[2]<a[1];++a[2])
       //......
         for(a[k]=1;a[k]<a[k-1];++a[k])
           ans+=f(q);
	cout<<ans;
}

其中, \(f(x) = a_m \times x^m + a_{(m - 1)} \times x^ {(m - 1)} + ... + a_1 \times x + a_0\)
现已知 程序中的常数变量 \(k, n , q\) , 多项式中的各项系数.
求 程序输出的 \(ans\) 的值

分析题意:

  • 显然 , 答案 = 循环次数 \(\times f(q)\) , 对两部分分别进行考虑

  • 对于程序的循环次数 :

    • 对于循环变量 \(a[1] , ... , a[k]\) , 观察程序 ,
      显然 , 有: \(a[1] > a[2] > .. > a[k]\) , 它们严格单调下降
      若使循环能够进行到最后一层 , 对答案做出贡献
      则有 : \(1 \le a[k], k \le a[1] \le n\)

    • \(n = 7, k = 4\) , 开始手玩数据 (感谢题解) :

      1. \(a1 = 4\) 时,\(a2 \sim a4\) 分别为 \(3,2,1\);
      2. \(a1 = 5\) 时,\(a1\sim a4\)可能为 \(\{4,3,2\},\{4,3,1\},\{4,2,1\},\{3,2,1\}\)
        即从\(4,3,2,1\) 中扔掉一个数,剩下的三个数即为 \(a2\sim a4\),方案数为 \(C(4,1)\) ;
      3. \(a1 = 6\) 时,从 \(5,4,3,2,1\) 中选出两个数扔掉,剩下三个数即为 \(a2\sim a4\),方案数为 \(C(5,2)\) ;
      4. \(a1 = 7\) 时,从 \(6,5,4,3,2,1\) 中选出三个数扔掉,剩下三个数即为 \(a2\sim a4\),方案数为 \(C(6,3)\) ;

      根据 组合数公式 : \(\text{C(n,m) = C(n - 1,m -1) + C(n-1, m)}\)\(\text{C(n, n-m) = C(n, m)}\)
      并将\(a1 = 4\) 时的方案数看做 \(C(3,0)\) , 则方案数为 :
      \(\ \ \ \ C(3, 0) + C(4, 1) + C(5, 2) + C(6, 3)\)
      \(= C(4, 0) + C(4, 1) + C(5, 2) + C(6, 3)\)
      \(= C(5,1) + C(5, 2) + C(6, 3)\)
      \(= C(6, 2) + C(6, 3)\)
      \(= C(7, 3)\)
      \(= C(7, 7 - 3)\)
      \(= C(7, 4)\)
      最终方案数 \(C(7, 4)\) 即: \(C(n, k)\)

    • 可以将上述性质 推广到所有 \(n, k\) 的取值情况上
      \(a1 = k\) 时, 方案数 为 : \(C(k - 1, 0)\)
      \(a1 = k + 1\) 时, 方案数 为 : \(C(k, 1)\)
      \(a1 = k + 2\) 时, 方案数 为 : \(C(k + 1, 2)\)
      \(a1 = n\) 时 , 方案数 为 : \(C(n - 1, n - k)\)

      方案总数即: \(C(n, n-k) = C(n, k)\)

    • 由于数据范围较小 ,
      可以使用 阶乘递推 \(+\) 乘法逆元法 求得组合数

  • 如何在较低 的 时间复杂度内求得 给定多项式 的值?
    考虑对式子进行转化:
    \(\ \ \ \ a_0 + a_1x + a_2x^2 +...+ a_mx^m\)
    \(=a_0+ x(a_1+a_2x+...+a_mx^{m-1})\)
    \(=a_0+ x(a_1 + x(a_2+...+a_mx^{m-2}) )\)
    \(=...\)
    \(=a_0+ x(a_1+x(a_2+x(....+a_m)...)\)
    明显可用递归 / 循环求解
    复杂度是 \(O(m)\) 级别的,

    上式 即求多项式的秦九韶算法

#include <cstdio>
#include <ctype.h>
#define int long long
const int mod =  1e9 + 7;
const int MARX = 5e5 + 10;
//=============================================================
int type, n, m, k, q, a[MARX];
int frac[MARX] = {1, 1};
//=============================================================
inline int read()
{
    int s = 1, w = 0; char ch = getchar();
    for(; !isdigit(ch); ch = getchar()) if(ch=='-') s =-1;
    for(; isdigit(ch); ch = getchar()) w = (w << 1) + (w << 3) +ch - '0';
    return s * w;
}
int qpow(int x, int y)//快速幂 
{
	int s = 1;
	for(; y;)
	{
	  if(y & 1) s = s * x % mod;
	  x = x * x % mod, y >>= 1;
	}
	return s;
}
int C(int n, int m)//递推求组合数 
{
	for(int i = 2; i <= n; i ++) frac[i] = frac[i - 1] * i % mod;
	int inv = qpow(frac[n - m] * frac[m] % mod, mod - 2);//费马小定理求逆元 
	return frac[n] * inv % mod;
}
int solve_fq()//求得 多项式的值 
{
	int ret = 0;
	for(int i = m + 1; i; i --) ret = ((ret * q) % mod + a[i]) % mod;
	return ret;
}
//=============================================================
signed main()
{
	n = read(), m = read(), k = read(), q = read() % mod;
	for(int i = 1; i <= m + 1; i ++) a[i] = read();
	printf("%lld",(C(n, k) * solve_fq()) % mod);
}
posted @ 2019-10-27 10:42  Luckyblock  阅读(100)  评论(0编辑  收藏  举报