[解题报告] 2018多校第四场B题 HDU-6333 莫队

题意是给你T个n,m求C(n,m)的前缀和。

定义 S(n, m) = \sum_{i = 0} ^ {m} {n \choose i}S(n,m)=∑ ​i=0 ​m ​​ ( ​i ​n ​​ ),不难发现 S(n, m) = S(n, m - 1) + {n \choose m}, S(n, m) = 2S(n - 1, m) - {n - 1 \choose m}S(n,m)=S(n,m−1)+( ​m ​n ​​ ),S(n,m)=2S(n−1,m)−( ​m ​n−1 ​​ )。也就是说,如果我们知道 S(n, m)S(n,m),就能以 O(1)O(1) 的代价计算出 S(n - 1, m), S(n, m - 1), S(n + 1, m), S(n, m + 1)S(n−1,m),S(n,m−1),S(n+1,m),S(n,m+1),可以采用莫队算法。定义 S(n, m) = \sum_{i = 0} ^ {m} {n \choose i}S(n,m)=∑ ​i=0 ​m ​​ ( ​i ​n ​​ ),不难发现 S(n, m) = S(n, m - 1) + {n \choose m}, S(n, m) = 2S(n - 1, m) - {n - 1 \choose m}S(n,m)=S(n,m−1)+( ​m ​n ​​ ),S(n,m)=2S(n−1,m)−( ​m ​n−1 ​​ )。也就是说,如果我们知道 S(n, m)S(n,m),就能以 O(1)O(1) 的代价计算出 S(n - 1, m), S(n, m - 1), S(n + 1, m), S(n, m + 1)S(n−1,m),S(n,m−1),S(n+1,m),S(n,m+1),可以采用莫队算法。定义S(n,m)为前缀和,则考虑已知S(n,m)的情况下能得到什么式子。经过一番简单推导可得:S(n,m)=S(n,m-1)+C(n,m)以及S(n,m)=2*S(n-1,m)-C(n-1,m)。因此我们可以用O(1)的时间转移。因此考虑莫队算法。

以前对莫队算法的理解局限在了区间询问,这题开了眼界了233

#include<iostream>
#include<string>
#include<cstring>
#include<stdlib.h>
#include<cstdio>
#include<stdio.h>
#include<set>
#include<map>
#include<deque>
#include<stack>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<list>
#include<bitset>
#include<sstream>
using namespace std;
#define lc n << 1
#define rc n << 1 | 1
#define pb push_back
#define mp make_pair
typedef long long ll;
typedef unsigned long long ull;
const double eps = 1e-8;
const int maxn = 1000010;
const int mod = 1e9 + 7;
const int INF = 0X3f3f3f3f;

int sz;
struct qry {
	int n, m;
	int id;
	bool operator <(const qry& y)
	{
		if (n / sz == y.n / sz)return m < y.m;
		return n / sz < y.n / sz;
	}
}q[maxn];
ll inv[maxn], jc[maxn], ans, aa[maxn];
int main()
{
	jc[0] = jc[1] = 1;
	inv[1] = 1;
	for (int i = 2;i < 100001;i++)
	{
		inv[i] = inv[mod%i] * (mod - mod / i) % mod;//逆元打表
		jc[i] = jc[i - 1] * i % mod;
	}
	for (int i = 2;i < 1e5 + 1;i++)
		inv[i] = inv[i - 1] * inv[i] % mod;//阶乘逆元
	inv[0] = 1;
	int T, n, m;
	scanf("%d", &T);
	sz = 0;
	for (int i = 0;i < T;i++)
	{
		scanf("%d%d", &q[i].n, &q[i].m);
		q[i].id = i;
		sz = max(sz, q[i].n);
	}
	sz = sqrt(sz);
	sort(q, q + T);
	n = 1, m = 0, ans = 1;//ans = S(n,m) = sigma_{1<=i<=n}C(n,i)
	auto C = [](int n, int m) {return jc[n] * inv[m] % mod * inv[n - m] % mod;};
	for (int i = 0;i < T;i++)
	{
		while (n < q[i].n) { n++; ans = ans * 2 - C(n - 1, m) + mod;ans %= mod; }
		while (m < q[i].m) { m++; ans = ans + C(n, m);ans %= mod; }
		while (m > q[i].m) { ans = ans - C(n, m) + mod;ans %= mod;m--; }
		while (n > q[i].n) { ans = ans + C(n - 1, m);ans = ans * inv[2] % mod;n--; }
		aa[q[i].id] = ans;
	}
	for (int i = 0;i < T;i++)
		printf("%lld\n", aa[i]);
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值