模拟赛 circle 题解

题意:有N个数,问有多少个x,\((x\leq T)\),满足这N个数分别+x后,异或和为S。每个数小于\(2^M\)
数位DP。
由于是加法,需要记录进位,因此从低位到高位DP。
只要记录下有几个进位,就可以根据这N的数的大小知道究竟是哪几个进位了。
\(dp(i,j,0/1)\)表示考虑到第i位,有j个进位,与T的大小关系为0/1的方案数。
可以提前预处理出转移,即\(g(i,j,0/1)\)表示考虑到第i位,有j个进位,当前位使用\(0/1\)造成的进位数。
时间复杂度\(O(NMlogN)\)(有排序),可以优化至\(O(NM)\)
代码(未经优化):

#include <stdio.h>
#include <stdlib.h>
#define ll long long
struct SPx
{
	ll z;int i;
};
SPx px[52][100010];
ll sz[100010],dp[52][100010][2];
int sl[52][100010][2],tm[100010],ss[52];
int sgn(ll x)
{
	if(x>0)
		return 1;
	else if(x<0)
		return -1;
	return 0;
}
int cmp(const void*a,const void*b)
{
	return sgn(((SPx*)b)->z-((SPx*)a)->z);
}
ll solve(int m,int n,ll S,ll T)
{
	if(T==0)return 0;
	T-=1;
	for(int i=m;i>=0;i--)
	{
		int b=bool(S&(1ll<<i)),bt=bool(T&(1ll<<i));
		for(int j=0;j<=n;j++)
		{
			if(i==m)
			{
				dp[i][j][0]=1;
				continue;
			}
			if((ss[i]+j)%2!=b)
				dp[i][j][0]=dp[i][j][1]=0;
			else
			{
				if(bt==0)
				{
					dp[i][j][0]=dp[i+1][sl[i][j][0]][0]+dp[i+1][sl[i][j][1]][1];
					dp[i][j][1]=dp[i+1][sl[i][j][0]][1]+dp[i+1][sl[i][j][1]][1];
				}
				else
				{
					dp[i][j][0]=dp[i+1][sl[i][j][0]][0]+dp[i+1][sl[i][j][1]][0];
					dp[i][j][1]=dp[i+1][sl[i][j][0]][0]+dp[i+1][sl[i][j][1]][1];
				}
			}
		}
	}
	return dp[0][0][0];
}
ll work(int m,int n,ll S,ll T)
{
	ll d=(1ll<<m);
	return (T/d)*solve(m,n,S,d)+solve(m,n,S,T%d);
}
int main()
{
	freopen("circle.in","r",stdin);
	freopen("circle.out","w",stdout);
	int n,m;ll S,T;
	scanf("%d%d%lld%lld",&n,&m,&S,&T);
	for(int i=0;i<n;i++)
	{
		ll a;
		scanf("%lld",&a);
		sz[i]=a=(a+1)%(1ll<<m);
		for(int j=0;j<m;j++)
		{
			if(a&(1ll<<j))
				ss[j]=(ss[j]+1)%2;
			px[j][i].i=i;
			px[j][i].z=a%(1ll<<(j+1));
		}
	}
	for(int i=0;i<m;i++)
		qsort(px[i],n,sizeof(SPx),cmp);
	for(int i=1;i<m;i++)
	{
		int s0=0;
		for(int j=n-1;j>=0;j--)
			tm[j]=tm[j+1]+bool(sz[px[i-1][j].i]&(1ll<<i));
		for(int j=0;j<=n;j++)
		{
			if(j>0)
				s0+=bool(sz[px[i-1][j-1].i]&(1ll<<i));
			sl[i][j][0]=s0;sl[i][j][1]=tm[j]+j;
		}
	}
	sl[0][0][0]=0;
	for(int i=0;i<n;i++)
		sl[0][0][1]+=(sz[i]%2);
	printf("%lld",work(m,n,S,T));
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值