牛客网暑期ACM多校训练营(第三场): C. Chiaki Sequence Reloaded(数位DP)

题目描述

Chiaki is interested in an infinite sequence a1, a2, a3, ..., which defined as follows:

Chiaki would like to know the sum of the first n terms of the sequence, i.e. . As this number may be very large, Chiaki is only interested in its remainder modulo (109 + 7).

输入描述:

There are multiple test cases. The first line of input contains an integer T (1 ≤ T ≤ 105), indicating the number of test cases. For each test case:
The first line contains an integer n (1 ≤ n ≤ 1018).

输出描述:

For each test case, output an integer denoting the answer.

题意:求上面的公式的绝对值的前缀和

打表可以看出a[i] = (i的2进制表示下:相邻位相同的个数 - 相邻位不同的个数),例如a[13(1101)] = 1-2 = -1

然后就可以求数位DP了,因为i的2进制最高只有64位,所以a[i]的取值范围大概是[-64, 64],可以考虑暴力每个值各出现多少次

预处理sum[x][y][z]表示当前暴力到第x位,且第x位为y,有多少个数满足a[]值为z(包含前导0)

例如sum[5][1][-3]就表示刚好有5位,且最高位为1,且满足a[k]为-3的二进制k的个数,有转移

for(i=2;i<=66;i++)
{
	for(j=1;j<=135;j++)		//因为数组下标不能为负数,所以需要整体移位:[-64,64]→[1,129]
	{
		sum[i][0][j] = (sum[i-1][0][j-1]+sum[i-1][1][j+1])%mod;
		sum[i][1][j] = (sum[i-1][1][j-1]+sum[i-1][0][j+1])%mod;
	}
}

之后对于每一个询问n,从n的最高位开始,如果当前位为1,那么很显然将这一位改成0之后后面所有的数字都可以01任取,与此同时你可以算出前面的(二进制相邻位相同的个数 - 相邻位不同的个数),这样只需要算上你预处理的sum[]就可以求出当前位对答案的贡献了,不过直接暴力sum[]还是会超时,所以还需要对sum再求一次前缀和

超时但好理解的程序:

#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
using namespace std;
#define LL long long
LL sum[88][2][222], a[88];
int main(void)
{
	LL T, n, i, j, len, ans, now, p;
	sum[1][0][100] = sum[1][1][100] = 1;
	for(i=2;i<=66;i++)
	{
		for(j=1;j<=200;j++)
		{
			sum[i][0][j] = sum[i-1][0][j-1]+sum[i-1][1][j+1];
			sum[i][1][j] = sum[i-1][1][j-1]+sum[i-1][0][j+1];
		}
	}
	scanf("%lld", &T);
	while(T--)
	{
		len = 0;
		scanf("%lld", &n);
		while(n)
		{
			a[++len] = n%2;
			n /= 2;
		}
		ans = now = 0;
		for(i=len;i>=1;i--)
		{
			p = 0;
			if(i!=len && a[i]!=a[i+1])  p = -1;
			else  if(i!=len) p = 1;
			if(a[i])
			{
				if(i!=len)
				{
					for(j=1;j<=200;j++)
						ans += sum[i][0][j]*abs(j-100+now-p);
				}
			}
			if(i!=len)
			{
				for(j=1;j<=200;j++)
					ans += sum[i][1][j]*abs(j-100);
			}
			now += p;
		}
		ans += abs(now);
		printf("%lld\n", ans);
	}
	return 0;
}

AC代码(附打表程序):

#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
using namespace std;
#define LL long long
#define mod 1000000007
LL sum[70][2][138], Q[70][2][138], a[88];
int main(void)
{
	LL T, n, i, j, k, len, ans, now, p;
	sum[1][0][65] = sum[1][1][65] = 1;
	for(i=2;i<=66;i++)
	{
		for(j=1;j<=135;j++)		//因为数组下标不能为负数,所以需要整体移位:[-64,64]→[1,129]
		{
			sum[i][0][j] = (sum[i-1][0][j-1]+sum[i-1][1][j+1])%mod;
			sum[i][1][j] = (sum[i-1][1][j-1]+sum[i-1][0][j+1])%mod;
		}
	}
	for(i=1;i<=66;i++)
	{
		for(k=1;k<=135;k++)
		{
			for(j=1;j<=135;j++)
			{
				Q[i][0][k] = (Q[i][0][k]+sum[i][0][j]*abs(j-k))%mod;
				Q[i][1][k] = (Q[i][1][k]+sum[i][1][j]*abs(j-k))%mod;
			}
		}
	}
	scanf("%lld", &T);
	while(T--)
	{
		len = 0;
		scanf("%lld", &n);
		while(n)
		{
			a[++len] = n%2;
			n /= 2;
		}
		ans = now = 0;
		for(i=len;i>=1;i--)
		{
			p = 0;
			if(i!=len && a[i]!=a[i+1])  p = -1;
			else  if(i!=len) p = 1;
			if(a[i])
			{
				if(i!=len)
					ans = (ans+Q[i][0][65-now+p])%mod;//sum[i][0][j]*abs(j-65+now-p);
			}
			if(i!=len)
				ans = (ans+Q[i][1][65])%mod;
			now += p;
		}
		ans = (ans+abs(now))%mod;
		printf("%lld\n", ans);
	}
	return 0;
}
/*#include<stdio.h>
#include<stdlib.h>
int F[10005], S[10005];
void Jud(int x)
{
	if(x<=1)
		printf("%d", x);
	else
	{
		Jud(x/2);
		printf("%d", x%2);
	}
}
int main(void)
{
	int i;
	for(i=1;i<=9999;i++)
	{
		if(i==1)
			F[i] = 0;
		else
		{
			if(i%4==2 || i%4==1)
				F[i] = F[i/2]-1;
			else
				F[i] = F[i/2]+1;
		}
		//printf("%-5d", i);
		//Jud(i);
		//printf(":   %d\n", F[i]);
		S[i] = abs(F[i])+S[i-1];
		if(i<=15 || i==7788)
			printf("%d\n", S[i]);
	}
	return 0;
}*/

猜你喜欢

转载自blog.csdn.net/jaihk662/article/details/81263085