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\) , 开始手玩数据
(感谢题解):- \(a1 = 4\) 时,\(a2 \sim a4\) 分别为 \(3,2,1\);
- \(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)\) ; - \(a1 = 6\) 时,从 \(5,4,3,2,1\) 中选出两个数扔掉,剩下三个数即为 \(a2\sim a4\),方案数为 \(C(5,2)\) ;
- \(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);
}