HDU 6467 简单数学题 (组合数学推导)
题意
题解
Step 1
把原式进行了最基本的变换,把 i 移到右边,并先枚举 j ,这里 i 从 0 开始枚举,并不影响答案,因为 C(j,0) 乘 0 后没有影响,但是这样方便后面的推导
Step 2
因为
这是最基本的组合数性质,所以把右边一坨加上自己的变式,再除以2
Step 3
把右上角换元,用 j - i 替换 i
Step 4
换元后,发现两坨可以合并,把同类项的系数加起来恰好等于 j
Step 5
因为
这也是组合数的性质之一,用组合意义可以解释为“从n个球中依次选0,1,2,...个的方案数之和就相当于每个数可以选与不选,也就是2^n”
所以就可以少枚举一层了。
我看这个式子后,想出了矩阵加速的解法
一个3*1的向量矩阵乘3*3的转移矩阵,向量里依次维护2^(j-1)、j * 2^(j-1)、sum。
转移矩阵也很好推,码码码……
……(Time Limit Exceeded)……
事后我算了一下复杂度,最大为O(300000*64*27)=O(518400000),好像过不了,只好继续推式子
Step 6
把 ×j 换成 ×[ (n + 1) - (n + 1 - j) ],然后分开,右边就相当于这样一个数
Step 7
(这一步可能难懂,请读者感性理解)
把Step 6里右边那个数拆开,
先设该数为 n 个数相加,n 个数初始为零
把 n×(1) 拆成 n 个 (1) 相加,然后依次加到 n 个数中
再把 (n-1)×(10) 拆成 n-1 个 (10) 相加,然后依次加到后 n-1 个数中
再把 (n-2)×(100) 拆成 n-2 个 (100) 相加,然后依次加到后 n-2 个数中
……
最后再把这 n 个数相加,发现
Step 8
把 2^j-1 的 1 提出来,直接在右边+n,顿时变得清爽
Step 9
这步是把它二进制展开,左边有n个连续的1,右边一个0,可以通过更高一位的 1 减去 (10) 得到
大功告成,可以直接用快速幂了!
Step 10
这步其实没什么必要,只是笔者想到可以(凑个整数)用欧拉定理优化,于是就用了,最大数据可以把常数除以 2
CODE
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<algorithm>
#define MAXN 505
#define MAXM 35
#define ENDL putchar('\n')
#define LL long long
#define DB double
#define lowbit(x) ((-x)&(x))
//#define int LL
using namespace std;
inline LL read() {
LL f = 1,x = 0;char s = getchar();
while(s < '0' || s > '9') {if(s == '-')f = -1;s = getchar();}
while(s >= '0' && s <= '9') {x = x * 10 + (s - '0');s = getchar();}
return x * f;
}
const int jzm = 1000000007;
int n,m,i,j,s,o,k;
int qkpow(int a,LL b,int zxy) {
int res = 1;
while(b > 0) {
if(b & 1) res = res *1ll* a % zxy;
a = a *1ll* a % zxy;
b >>= 1;
}
return res % zxy;
}
int main() {
LL N;
while(scanf("%lld",&N) == 1) {
int ans = (qkpow(2,N % (jzm-1),jzm) + (jzm-1)) % jzm *1ll* (N%jzm + 1ll) % jzm;
ans = (ans +0ll+jzm - qkpow(2,(N+1) % (jzm-1),jzm)) % jzm;
(ans += (N+2ll) % jzm) %= jzm;
printf("%d\n",ans);
}
return 0;
}