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

Problem

在这里插入图片描述

Hint

在这里插入图片描述

Solution

  • 首先,考虑类似差分的容斥。
  • f a , b f_{a,b} fa,b表示x在[0,a-1]范围内,y在[0,b-1]范围内的答案。则原 A n s = 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=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
  • 问题转化为求解 f a , b f_{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 = z x\oplus y=z xy=z,那么若确定了z的任意段,就确定了x的任意段。因为y的任意段是已知的。也就是说,对于同一z,任意段只有1组方案。
  • 但是,x和y的任意2段均不确定。假设枚举y的任意2段(设任意2段长度为l2,则从0枚举到 2 l 2 − 1 2^{l2}-1 2l21),就可以确定x的任意2段了。因此,对于同一z,任意2段有 2 l 2 2^{l2} 2l2组方案。
  • 那么,假设确定了z,两个任意段就有 1 ∗ 2 l 2 = 2 l 2 1*2^{l2}=2^{l2} 12l2=2l2组方案。

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

  • 时间复杂度: O ( l o g 2 2 1 0 18 ) ≈ O ( 6 0 2 ) 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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值