1. 程式人生 > >【JZOJ5894】【NOIP2018模擬10.5】同餘方程(容斥+計數問題+類數位DP)

【JZOJ5894】【NOIP2018模擬10.5】同餘方程(容斥+計數問題+類數位DP)

Problem

在這裡插入圖片描述

Hint

在這裡插入圖片描述

Solution

  • 首先,考慮類似差分的容斥。
  • fa,bf_{a,b}表示x在[0,a-1]範圍內,y在[0,b-1]範圍內的答案。則原Ans=fr1+1,r2+1fl1,r2+1fr1+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}
  • 問題轉化為求解fa,bf_{a,b}
  • 我們列舉一個i,表示x在(二進位制)第i位比a小了。亦即a的第i位=1,而x的第i為=0;而x的前i-1位均與a同。這樣,x後面的位就可以亂填了。
  • 同時列舉一個j,表示y在第j位比b小了。
  • 這樣的話,a、b就會分成如下三段:
    在這裡插入圖片描述
  • 如圖,藍線以左為已知,兩線間為任意,綠線以右為任意2。
  • 假設xy=zx\oplus y=z,那麼若確定了z的任意段,就確定了x的任意段。因為y的任意段是已知的。也就是說,對於同一z,任意段只有1組方案。
  • 但是,x和y的任意2段均不確定。假設列舉y的任意2段(設任意2段長度為l2,則從0列舉到2l212^{l2}-1),就可以確定x的任意2段了。因此,對於同一z,任意2段有2l22^{l2}組方案。
  • 那麼,假設確定了z,兩個任意段就有12l2=2l21*2^{l2}=2^{l2}
    組方案。
  • 但是,我們還要使mzm|z
  • 設任意段長為l1,則z的取值範圍是[0,2l1+l21][0,2^{l1+l2}-1]
  • 我們設已知段的異或和為p,則xyx\oplus y的取值範圍是[p,p+2l1+l21][p,p+2^{l1+l2}-1]
  • 求出其中有tot個數是m的倍數,那麼合法的z有tot個。令ans+=tot2l2ans+=tot*2^{l2}
  • 時間複雜度:O(log221018)O(602)O(log_2^210^{18})≈O(60^2)

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);
}