小学生计数题
第四题 小学生计数题
提交文件: counting.cpp
输入文件: counting.in
输出文件: counting.out
时间空间限制: 1 秒, 256 MB
作为 GDOI 的组题人,小 Y 需要整理手中已有的题目,考虑它们的难度以及所考察的知识点,然后将它们组成数套题目。小 Y 希望先能组出第一套题目,为了整套题目具有良好的区分度,在一套题目中:
? 所有题目的难度需要能排成等差数列;(也就是说,若将所有题目按难度从小到大排序,那么每相邻两
题的难度的差相等,这个差叫做公差)
? 每道题目的难度都是公差的倍数,公差不为 0;
? 需要有不少于 L 道题,不多于 R 道题。
现在小 Y 手里已经有了 m 道题目,其中难度为 \(a_i\) 的题有 \(c_i\) 道\((1 ≤ i ≤ n)\)。小 Y 希望能够道,他有多少种不同的方式能够组出一套题目。
在这道题目中,我们认为两种组题方式不同当且仅当 \(?k(1 ≤ k ≤ m)\),使得一种方案包含第 \(k\) 道题而另一种方案不包含。由于答案可能很大,输出答案对 998244353 取模。
输入格式
第一行三个整数 \(n, m, L, R\),分别表示有多少种不同难度的题目、题目总数、一套题的题数下限和上限。
接下来 \(n\) 行,每行两个正整数 \(a_i\)
, \(c_i\),表示有 \(c_i\) 道难度为 \(a_i\) 的题\((1 ≤ i ≤ n)\)
输出格式
输出一个数,表示组题方案数对 998244353 取模的值
样例数据
6 12 2 3
2 2
4 2
6 2
8 2
12 2
16 2
64
数据范围
对于所有测试点,\(1 ≤ n, c_i ≤ 10^5,0 ≤ a_i ≤ 10^5,m =∑c_i ≤ 10^5,2 ≤ L ≤ R ≤ m\),\(a_i\) 两两不同。
测试点 | n | m | \(a_i\) | 特殊性质 1 | 特殊性质 2 |
---|---|---|---|---|---|
1-4 | $≤ 20 $ | \(≤ 20\) | \(≤ 20\) | - | - |
5-6 | $≤ 300 $ | \(≤ 300\) | $ ≤ 300$ | - | - |
7-8 | \(≤ 2000\) | \(≤ 2000\) | \(≤ 2000\) | √ | - |
9-10 | \(≤ 2000\) | \(≤ 2000\) | \(≤ 2000\) | - | √ |
11-12 | \(≤ 2000\) | \(≤ 2000\) | \(≤ 2000\) | - | - |
13-14 | \(≤ 10^5\) | \(≤ 10^5\) | \(≤ 10^5\) | √ | - |
15-16 | \(≤ 10^5\) | \(≤ 10^5\) | \(≤ 10^5\) | - | √ |
17-20 | \(≤ 10^5\) | \(≤ 10^5\) | \(≤ 10^5\) | - | - |
特殊性质 1:满足 \(R ? L ≤ 10\)
特殊性质 2:满足所有 \(c_i = 1\)
要求一定是倍数,那么就用调和级数行枚举。每次看\(a_i\)为\(i\)的倍数的情况下看当中有多少种选择方案。我们把所有\(a_i\)是\(i\)的倍数的情况提取出一个序列。
首先在输入时可以知道每种难度有多少个题可以选,枚举时每次选择一段连续的区间,区间要求长度大于\(l\)小于\(r\),然后将里面的所有难度题目数量相乘就是在这个区间中合法的数量。很容易发现,如果有\(0\)那么答案就是\(0\),所以0把序列分成了很多段,我们每次只截取一段计算。同时所有数相乘可以通过对他求一个前缀积的形式,设现在区间是\(l\)到\(r\),前缀积为\(t\),那么可以用\(t_r\div t_{l-1}\)来知道中间所有数的乘积。当然,由于要取模,所以还要用逆元。
整理一下思路,对于一个序列,我们先提取出所有的不含0的段,然后对于每个段,我们枚举长度在\(l\)到\(r\)之间的区间,然后对通过前缀积求出他的中间所有数的乘积,可以通过特殊性质1.
考虑再次优化,我们对于一个位置\(x\),如果把他作为区间的结尾,然后可以求出可能的开头,就是那结尾段减去\(l\)和\(r\)就是开头允许的范围\(tl,tr\),设那个位置逆元为\(v_i\),那么就求\(t_x\times v_i\)的和.但是我们可以对\(v\)求一个前缀和s,枚举\(x\)时可以用乘法分配律,直接加上\(t_x\times (s_{tr}-s_{tl-1})\)。复杂度降到\(nlog^2n\),可以通过此题。注意\(0\le a_i\),不要让数组越界。
#include
using namespace std;
const int N=1e5+5,mod=998244353;
int n,m,l,r,v[N],lst,x,y,ret,s[N],t[N];
int pown(int x,int y)
{
if(y==0)
return 1;
int p=pown(x,y>>1);
if(y&1)
return 1LL*p*p%mod*x%mod;
return 1LL*p*p%mod;
}
int qumo(int x)
{
return (x%mod+mod)%mod;
}
void solve(int c,int x,int y)//公差为c,从x到y的答案。
{
s[x-1]=t[x-1]=1;
for(int i=x;i<=y;i++)
s[i]=1LL*s[i-1]*v[c*(i-1)]%mod;
for(int i=x;i<=y;i++)
t[i]=(t[i-1]+pown(s[i-1],mod-2))%mod;
for(int i=x;i<=y;i++)//枚举结尾
{
int p=max(x,i-r+1),q=i-l+1;
if(p>q)
continue;
ret=(ret+1LL*s[i]*qumo(t[q]-t[p-1])%mod)%mod;
}
}
int main()
{
// freopen("counting.in","r",stdin);
// freopen("counting.out","w",stdout);
scanf("%d%d%d%d",&n,&m,&l,&r);
for(int i=1;i<=n;i++)
scanf("%d%d",&x,&y),v[x]=y;
for(int i=1;i<=100000;i++)//枚举公差
{
lst=0;
int j;
for(j=0;j*i<=100000;j++)
{
if(!v[j*i])
{
solve(i,lst+1,j);
lst=j+1;
}
}
solve(i,lst+1,j);
}
printf("%d",ret);
return 0;
}