校内NOI模拟测试007T1 送分(score) “简单”的计数
问题描述
良心出题人准备为大家送出大量的分数。
出题人生成了一个序列,其中第\(𝑖\)个元素为\(a_i\)。
定义一个集合\(𝑆\)的分值为
\[F(S,x) = \sum_{T \subseteq S} G(T)·x^{|T|}
\]
其中𝑥为给 定参数,𝐺(𝑇)表示𝑇中所有元素的异或和,空集的异或和为0。 出题人每次会选择一个区间\([𝑙, 𝑟]\)和一个参数\(𝑥\),令第\(𝑙\)到第\(𝑟\)个元素组成的集合为\(𝑆\),出题人会送给一位同学\(𝐹(𝑆, 𝑥)\)的分数。 你想知道每个同学获得了多少分数,因为分数实在太多了, 你只想要知道分数对\(998244353\)取模后的结果。
题意简述
给一个序列a,设一个区间S分值为
\[F(S,x) = \sum_{T \subseteq S} G(T)·x^{|T|}
\]
有m个询问,每次给\(l\),\(r\),\(x\),求\(F([l,r],x)\)
即,求给定区间内的所有集合的异或和*\(x^{集合大小}\)
集合为可重集合。
分析
因为式子中有一个集合大小的贡献,所以无法预处理,只能直接搞。
考虑拆位,设这个区间长度为\(n\),区间内的第k位有\(m\)个\(1\)。
若\(1\)的个数为偶数,则没有贡献。而\(0\)的贡献很好求,虽然不会对答案有直接影响,但是充当了集合的大小的一部分,所以它的贡献是一个系数。
可以计算出贡献:
\[\sum_{i=0}^{n-m}\sum_{j \nmid 2}^m{C_{n-m}^i \cdot x^i \times C_m^j \cdot x^j}
\]
将无关项分离:
\[\sum_{i=0}^{n-m}{C_{n-m}^i \cdot x^i }\times { \sum_{j \nmid 2}^m C_m^j \cdot x^j}
\]
观察到形如二项式定理
\[\sum_{i=0}^n {C_n^i \cdot x^i} = (x+1)^n
\]
的两个式子,左边的可以用二项式定理直接变成\((x+1)^n\),即
\[(x+1)^n = { \sum_{j \nmid 2}^m C_m^j \cdot x^j}
\]
那么右边的这个\(j \texttt{ mod } 2 ==1\)怎么转化呢?类似共轭,
\[(1+(-x))^n=\sum_{i=0}^n {C_n^i \cdot (-x)^i}
\]
两式相减
\[(x+1)^n-(-x+1)^n=2 \sum_{i \nmid 2} {C_n^i \cdot x^i}
\]
成功了!再除以\(2\)就是右半部分了。
所以答案就是
\[(x+1)^{n-m} \cdot \frac{(x+1)^m-(1-x)^m}{2}
\]
用前缀和计算区间内第\(k\)位上\(1\)的个数\(m\),再加上快速幂就好了,记得\(\texttt{long long}\),取模,除以\(2\)的话记得用逆元。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<vector>
#define p 998244353
#define IL inline
#define re register
#define LL long long
#define ULL unsigned int
#define re register
#define debug printf("Now is %d\n",__LINE__);
using namespace std;
template<class T>inline void read(T&x)
{
char ch=getchar();
while(!isdigit(ch))ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
}
inline int read()
{
int x=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x;
}
int G[55];
template<class T>inline void write(T x)
{
int g=0;
if(x<0) x=-x,putchar('-');
do{G[++g]=x%10;x/=10;}while(x);
for(re int i=g;i>=1;--i)putchar('0'+G[i]);putchar('\n');
}
int a[100010];
LL sum[100010][32];
int n;
int m;
LL qpow(LL a,LL b)
{
int ans=1;
for(;b;b>>=1,a=a*a%p)
if(b&1) ans=ans*a%p;
return ans;
}
int main()
{
n=read();
m=read();
for(LL i=1;i<=n;i++)
{
a[i]=read();
for(int j=30;j>=0;j--)
sum[i][j]=sum[i-1][j]+((a[i]>>j)&1);
}
for(LL l,r,x,i=0;i<m;i++)
{
l=read();
r=read();
x=read();
LL ans=0;
for(LL j=30;j>=0;j--)
{
LL N=r-l+1ll,M=(sum[r][j]-sum[l-1ll][j]+p)%p;
ans=(ans+(1ll<<j)%p*qpow(x+1ll,N-M)%p*(qpow(x+1ll,M)+p-qpow((1ll-x+p)%p,M))%p*qpow(2,p-2)%p)%p;
}
cout<<ans<<endl;
}
return 0;
}
结语
多取模啊!多取模!不然的话就会……