jzoj5894. 【NOIP2018模拟10.5】同余方程
题目描述
30%
直接暴力搞
60%
可以打个表找规律,发现大致可以分成log段连续的数
然后随便搞
100%
考虑用容斥的思想,把询问拆成四个
所以就变成了求
显然如果直接枚举还是会挂,考虑枚举i(j)中第一个与l(r)不同的位(前提是i≤l,j≤r)
(假设l不同的位比r后)
那么用红框表示已知部分,蓝框表示未知部分
所以异或之后就会变成这样
中间的紫色部分表示一半已知,一半未知,后面的表示完全未知
左边的部分可以直接l和r异或得出(要考虑末尾i/j不同时,那一位要取反,除非i=j)
显然未知部分的所有情况都会被异或出来
因为,而中间已知a,所以每个c只对应一个b
而后面部分a和b都不知道,所以每个c会出现有2^(蓝色部分长度)次
所以每次的贡献就是
((红色部分,后面的所有情况)的贡献)*2^(蓝色部分长度)
中间的贡献可以直接求
最后考虑i=l、j=r的情况,最终答案就是
code
#include <iostream>
#include <cstdlib>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define min(a,b) (a<b?a:b)
#define mod 998244353
using namespace std;
bool a[61];
bool b[61];
long long p[61];
long long P[61];
long long l1,r1,l2,r2,m,A,B;
long long get(long long l,long long r)
{
if (l>r) return 0;
if (l)
return (r/m-(l-1)/m)%mod;
else
return (r/m+1)%mod;//要考虑0
}
long long ans(long long l,long long r)
{
int i,j,mx;
long long L=l,R=r,Ans=0,S=l^r;
A=-1;
B=-1;
while (l)
{
a[++A]=l&1;
l>>=1;
}
while (r)
{
b[++B]=r&1;
r>>=1;
}
fo(i,0,A)
if (a[i])
Ans=(Ans+get(((S/p[i])*p[i])^p[i],((S/p[i])*p[i])^p[i]+p[i]-1))%mod;//i=l的情况
fo(i,0,B)
if (b[i])
Ans=(Ans+get(((S/p[i])*p[i])^p[i],((S/p[i])*p[i])^p[i]+p[i]-1))%mod;//j=r的情况
fo(i,0,A)
if (a[i])
{
fo(j,0,B)
if (b[j])
{
mx=max(i,j);
if (i!=j)//特判i=j
Ans=(Ans+get(((S/p[mx])*p[mx])^p[mx],((S/p[mx])*p[mx])^p[mx]+p[mx]-1)*P[min(i,j)])%mod;
else
Ans=(Ans+get(((S/p[mx])*p[mx]),((S/p[mx])*p[mx])+p[mx]-1)*P[min(i,j)])%mod;
}
}
Ans+=((S%m)==0);//当i=l且j=r时
return Ans;
}
void init()
{
int i;
p[0]=1;
P[0]=1;
fo(i,1,60)
{
p[i]=(p[i-1]<<1);
P[i]=p[i]%mod;
}
}
int main()
{
freopen("mod.in","r",stdin);
freopen("mod.out","w",stdout);
init();
scanf("%lld%lld%lld%lld%lld",&l1,&r1,&l2,&r2,&m);
printf("%lld\n",((ans(r1,r2)-ans(l1-1,r2)-ans(r1,l2-1)+ans(l1-1,l2-1))%mod+mod)%mod);
fclose(stdin);
fclose(stdout);
return 0;
}