jzoj5894. 【NOIP2018模拟10.5】同余方程

44 篇文章 0 订阅
4 篇文章 0 订阅

题目描述

在这里插入图片描述

30%

直接暴力搞

60%

可以打个表找规律,发现大致可以分成log段连续的数
然后随便搞

100%

考虑用容斥的思想,把询问拆成四个
所以就变成了求
a n s ( l , r ) = ∑ i = 0 l ∑ j = 0 r ( i ⊕ j ) ≡ 0    ( m o d    m ) ans(l,r)=\sum_{i=0}^{l}{\sum_{j=0}^{r}{(i⊕j)≡0\;(mod \; m)}} ans(l,r)=i=0lj=0r(ij)0(modm)

显然如果直接枚举还是会挂,考虑枚举i(j)中第一个与l(r)不同的位(前提是i≤l,j≤r)
在这里插入图片描述
(假设l不同的位比r后)
那么用红框表示已知部分,蓝框表示未知部分
在这里插入图片描述
所以异或之后就会变成这样
在这里插入图片描述
中间的紫色部分表示一半已知,一半未知,后面的表示完全未知
左边的部分可以直接l和r异或得出(要考虑末尾i/j不同时,那一位要取反,除非i=j)


显然未知部分的所有情况都会被异或出来
因为 a ⊕ b = c a⊕b=c ab=c,而中间已知a,所以每个c只对应一个b
而后面部分a和b都不知道,所以每个c会出现有2^(蓝色部分长度)次

所以每次的贡献就是
((红色部分,后面的所有情况)的贡献)*2^(蓝色部分长度)
中间的贡献可以直接求

最后考虑i=l、j=r的情况,最终答案就是
a n s ( r 1 , r 2 ) − a n s ( l 1 − 1 , r 2 ) − a n s ( r 1 , l 2 − 1 ) + a n s ( l 1 − 1 , l 2 − 1 ) ans(r1,r2)-ans(l1-1,r2)-ans(r1,l2-1)+ans(l1-1,l2-1) ans(r1,r2)ans(l11,r2)ans(r1,l21)+ans(l11,l21)

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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值