模拟赛 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;
}