2019牛客暑期多校训练营(第七场)H

Pair 数位dp

数位dp还是写得太少了,开始时完全不知道怎么下手,还是看了别人的博客才算是理解了一点。

题意:求满足下列条件之一的数对的个数
1  x & y>C
2  x^y<C
1 &lt; = x &lt; = A 1&lt;=x&lt;=A and 1 &lt; = y &lt; = B 1&lt;=y&lt;=B

如果我没看错题解的话,题解中除了叙述题意的词语之外,总共就10个字的题解,菜鸡表示看哭了;不过做了之后发现好像也不是那么难。

思路:把A,B,C用二进制表示,在二进制下进行数位dp,从高位开始,dp[len][st1][st2][limt1][limt2]
len:表示当前处理的位置
st1,st2:分别表示条件1和条件2的状态。0,表示有可能满足,1表示已经满足了,2表示已经不可能满足了
st1,st2:这个东西很多数位dp都有的,表示当前的数是否有限制
转移:

if ((st1 == 0 && x < dat[len] || st1 == 2) && (st2 == 0 && y > dat[len] || st2 == 2))continue;   //当前两个条件都不能满足了,
//st1==0也可以这样理解,之前的所有位置都和c相同
if (st1 == 0) {			//当前可能满足,如果x>dat[len],说明当前就已经比C大了,那么以后无论怎么选都是ok的
	if (x > dat[len])ss1 = 1;
	else if (x < dat[len])ss1 = 2;//当前最高为已经比c小了,那么以后的位置就算全部是1也可能大于c了
	else ss1 = 0;	//当前位和c相同,无法判断,需要往下处理
}
if (st2 == 0) {			//这里的分析和上面类似,注意这里的条件是^就ok了。
	if (y > dat[len])ss2 = 2;
	else if (y < dat[len])ss2 = 1;
	else ss2 = 0;
}

剩下需要写的就是数位dp的老套路了。
结果:

printf("%lld\n",solve(A,B,C)-min(A,C-1)-min(B,C-1)-1);

为什么要减去这些东西呢,题目中的x,y都是大于1的,而我们dp的时候0也被计算进去了,所以要减去这样不合法的,考虑A=0的时候,那么所有B中小于C的数,都被计算了,那么需要减去min(B,C-1),同理B=0的时候,也需要减去min(A,C-1),最后一个就是A,B同时为0了呗。

当然这里还有一个可有可无的优化,我没加优化时间是4ms,加了是3ms。
注意到对于给的两个条件,只要满足其中的一个,那么另一个条件是否满足就没有关系了。
所以每次开始的时候可以先判断一下当前是否已经满足其中一个条件,如果已经满足了,就可结束此次dp了;

if (st1 == 1 || st2 == 1) {
	ll a = num[len], b = num[len];
	if (limt1)a = num1[len] +1;
	if (limt2)b = num2[len] +1;
	return a * b;
}

num[len]:在没有限制条件下的len个位所能表示的最高数量,这个可以提前预处理出来
num1[len],num2[len]:分别表示A,B的len个位所能表示的最大值

注意:考虑的0的存在,所以是num1[len]+1,对于num的预处理,初始值需要从2可以。也可以这样理解,一个位可以表示两个数

这里给出我优化了的代码(当然有参考了其他大神的杰作QWQ):

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll dp[40][3][3][2][2];
ll dat1[40], dat2[40], dat[40], num1[40], num2[40], num[40];
void init() {
	num[0] = 2;
	for (int i = 1; i < 33; i++)num[i] = num[i - 1] * 2;
}

ll dfs(int len, int st1, int st2, bool limt1, bool limt2) {
	if (st1 == 2 && st2 == 2)return 0;
	if (len == -1)return st1 == 1 || st2 == 1;
	if (st1 == 1 || st2 == 1) {
		ll a = num[len], b = num[len];
		if (limt1)a = num1[len] +1;
		if (limt2)b = num2[len] +1;
		return a * b;
	}
	if (dp[len][st1][st2][limt1][limt2] != -1)return dp[len][st1][st2][limt1][limt2];
	int up1 = limt1 ? dat1[len] : 1, up2 = limt2 ? dat2[len] : 1;
	ll ans = 0;
	for (int i = 0; i <= up1; i++)for (int j = 0; j <= up2; j++) {
		int x = i & j, y = i ^ j, ss1 = st1, ss2 = st2;
		if ((st1 == 0 && x < dat[len] || st1 == 2) && (st2 == 0 && y > dat[len] || st2 == 2))continue;
		if (st1 == 0) {
			if (x > dat[len])ss1 = 1;
			else if (x < dat[len])ss1 = 2;
			else ss1 = 0;
		}
		if (st2 == 0) {
			if (y > dat[len])ss2 = 2;
			else if (y < dat[len])ss2 = 1;
			else ss2 = 0;
		}
		ans += dfs(len-1,ss1,ss2,limt1&&i==up1,limt2&&j==up2);
	}
	return dp[len][st1][st2][limt1][limt2] = ans;
}
ll solve(ll A, ll B, ll C) {
	int cnt1 = 0, cnt2 = 0, cnt3 = 0, cnt = 0;
	for (int i = 0; i < 33; i++)dat[i] = dat1[i] = dat2[i] = 0;
	while (A)dat1[cnt1++] = A & 1, A >>= 1;
	while (B)dat2[cnt2++] = B & 1, B >>= 1;
	while (C)dat[cnt3++] = C & 1, C >>= 1;
	cnt = max(max(cnt1,cnt2),cnt3)-1;
	for (int i = cnt; i>=0; i--) {
		num1[i] = num2[i] = 0;
		for (int j = i; j>=0; j--) {
			num1[i] = num1[i] << 1 | dat1[j];
			num2[i] = num2[i] << 1 | dat2[j];
		}
	}
	return dfs(cnt,0,0,true,true);
}

int main() {
	init();
	int t; scanf("%d",&t);
	while (t--) {
		memset(dp, -1, sizeof(dp));
		ll A, B, C; scanf("%lld%lld%lld",&A,&B,&C);
		printf("%lld\n",solve(A,B,C)-min(A,C-1)-min(B,C-1)-1);
	}
	return 0;
}
发布了70 篇原创文章 · 获赞 5 · 访问量 7176

猜你喜欢

转载自blog.csdn.net/xiaonanxinyi/article/details/98963366