【JZOJ5894】【NOIP2018模拟10.5】同余方程(容斥+计数问题+类数位DP)

本文介绍了一种使用二进制容斥原理和异或操作优化的算法,用于解决特定区间内整数对的计数问题。通过枚举关键位,将问题分解并利用预计算技巧,实现高效求解。算法复杂度为O(log^2 10^18),适用于大数据量下的快速计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem

在这里插入图片描述

Hint

在这里插入图片描述

Solution

  • 首先,考虑类似差分的容斥。
  • fa,bf_{a,b}fa,b表示x在[0,a-1]范围内,y在[0,b-1]范围内的答案。则原Ans=fr1+1,r2+1−fl1,r2+1−fr1+1,l2+fl1,l2Ans=f_{r_1+1,r_2+1}-f_{l_1,r_2+1}-f_{r_1+1,l_2}+f_{l_1,l_2}Ans=fr1+1,r2+1fl1,r2+1fr1+1,l2+fl1,l2
  • 问题转化为求解fa,bf_{a,b}fa,b

  • 我们枚举一个i,表示x在(二进制)第i位比a小了。亦即a的第i位=1,而x的第i为=0;而x的前i-1位均与a同。这样,x后面的位就可以乱填了。
  • 同时枚举一个j,表示y在第j位比b小了。
  • 这样的话,a、b就会分成如下三段:
    在这里插入图片描述
  • 如图,蓝线以左为已知,两线间为任意,绿线以右为任意2。

  • 假设x⊕y=zx\oplus y=zxy=z,那么若确定了z的任意段,就确定了x的任意段。因为y的任意段是已知的。也就是说,对于同一z,任意段只有1组方案。
  • 但是,x和y的任意2段均不确定。假设枚举y的任意2段(设任意2段长度为l2,则从0枚举到2l2−12^{l2}-12l21),就可以确定x的任意2段了。因此,对于同一z,任意2段有2l22^{l2}2l2组方案。
  • 那么,假设确定了z,两个任意段就有1∗2l2=2l21*2^{l2}=2^{l2}12l2=2l2组方案。

  • 但是,我们还要使m∣zm|zmz
  • 设任意段长为l1,则z的取值范围是[0,2l1+l2−1][0,2^{l1+l2}-1][0,2l1+l21]
  • 我们设已知段的异或和为p,则x⊕yx\oplus yxy的取值范围是[p,p+2l1+l2−1][p,p+2^{l1+l2}-1][p,p+2l1+l21]
  • 求出其中有tot个数是m的倍数,那么合法的z有tot个。令ans+=tot∗2l2ans+=tot*2^{l2}ans+=tot2l2

  • 时间复杂度:O(log221018)≈O(602)O(log_2^210^{18})≈O(60^2)O(log221018)O(602)

Code

#include <bits/stdc++.h>
#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;

const ll mo=998244353,p59=1ll<<59;
ll l1,r1,l2,r2,m,i,j,x,pa,y,pb,L1,L2,L3,p,p1,tot;

ll dp(ll a,ll b)
{
	x=p59; pa=0; ll ans=0;
	fd(i,59,0)
	{
		pa<<=1;
		if(x&a)
		{
			y=p59; pb=0;
			fd(j,59,0)
			{
				pb<<=1;
				if(y&b)
				{
					L1=59-max(i,j);	L2=abs(i-j); L3=min(i,j);
					p=(i>j ? pa^(pb>>L2) : pb^(pa>>L2)) << L2+L3;
					p1=p+(1ll<<L2+L3)-1;
					tot= (p1/m - p/m + (p%m==0)) % mo;
					(ans+=tot*((1ll<<L3)%mo))%=mo;
				}
				pb+=(bool)(y&b); y>>=1;
			}
		}
		pa+=(bool)(x&a); x>>=1;
	}
	return ans;
}

int main()
{
	freopen("mod.in","r",stdin);
	freopen("mod.out","w",stdout);
	scanf("%lld%lld%lld%lld%lld",&l1,&r1,&l2,&r2,&m);
	printf("%lld",(dp(r1+1,r2+1)-dp(l1,r2+1)-dp(r1+1,l2)+dp(l1,l2)+2*mo)%mo);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值