牛客网暑期ACM多校训练营(第七场)H.Pair 数位DP

19 篇文章 0 订阅

H.Pair


题意

给出三个数 A , B , C A,B, C A,B,C

求出 p a i r &lt; i , j &gt; pair&lt;i, j &gt; pair<i,j> 对数使得 i ∧ j &gt; C i\land j &gt; C ij>C或者 i ⊕ j &lt; C i \oplus j&lt;C ij<C

1 ≤ i ≤ A , 1 ≤ j ≤ B 1 \leq i \leq A,1\leq j \leq B 1iA1jB

思路

  • 首先易知这是求二进制问题

  • 可以将问题转化为 * *总个数减去 i ∧ j ≤ C i\land j\leq C ijC并且 i ⊕ j ≥ C i \oplus j \ge C ijC的个数 * *
    - 1 ≤ i ≤ A , 1 ≤ j ≤ B 1 \leq i \leq A,1\leq j \leq B 1iA1jB并且符合 i ∧ j &gt; C i\land j&gt;C ij>C或者 i ⊕ j &lt; C i \oplus j &lt; C ij<C

  • 在一个区间内符合 f ( i ) f(i) f(i),看起来像** 数位DP**
    -由于处理二进制,我们可以 * 同时统计两个数的01进制*
    -在数位DP时维护两个 l i m i t , l e a d limit,lead limitlead

状态

d p [ p o s ] [ s t 1 ] [ s t 2 ] [ l e a d 1 ] [ l e a d 2 ] [ l i m i t 1 ] [ l i m i t 2 ] dp[pos][st1][st2][lead1][lead2][limit1][limit2] dp[pos][st1][st2][lead1][lead2][limit1][limit2]

pos为当前位置,st1维护条件1成立,st2维护条件2成立

直接将所有状态记录下来,反之复杂度也不会爆

** 由于不存在等于0的情况,所以记录前导0的状态是必须的**

当然,先处理出包含0的对数,再减去和0达成条件的对数也可以(前导0就不必要)

代码

预处理
	LL part(int x, int y, int z) {
	memset(a, 0, sizeof(a));
	memset(b, 0, sizeof(b));
	memset(c, 0, sizeof(c));
	memset(dp, -1, sizeof(dp));
	LL res = LL(x) * y;		//转化问题为总个数减去并且的情况
	int l1, l2, l3; l1 = l2 = l3 = 0;	//拆分为二进制数
	while (x)a[++l1] = x & 1, x >>= 1;
	while (y)b[++l2] = y & 1, y >>= 1;
	while (z)c[++l3] = z & 1, z >>= 1;
	len = max(l1, max(l2, l3));
	return res - dfs(1, 0, 0, 1, 1, 1, 1);
}
数位DP
LL dp[maxn][2][2][2][2][2][2];
LL dfs(int pos, bool st1, bool st2, bool lead1, bool lead2, bool limit1, bool limit2)
//pos为位置,st1为条件1成立,st2为条件2成立,lead为前导0,limit为最高位
{
	if (pos > len) return (!lead1) && (!lead2);	//A>=1&&B>=1不存在i==0||j==0的对
	if (dp[pos][st1][st2][lead1][lead2][limit1][limit2] != -1)
		return dp[pos][st1][st2][lead1][lead2][limit1][limit2];	//记忆化
	if (st1 && st2 && !limit1 && !limit2 && !lead1 && !lead2) //由于01所以这个处理反而慢了qwq
		return (LL(1) << (len - pos + 1)) * (1 << (len - pos + 1));
	LL res = 0;
	int top1 = limit1 ? a[len - pos + 1] : 1;
	int top2 = limit2 ? b[len - pos + 1] : 1;
	for (int i = 0; i <= top1; i++) {
		for (int j = 0; j <= top2; j++) {
			if (!st1 && (i & j) > c[len - pos + 1]) continue;//i&j>C不符合条件
			if (!st2 && (i ^ j) < c[len - pos + 1]) continue;//i^j<C不符合条件
			res += dfs(pos + 1, st1 || (i & j) < c[len - pos + 1], st2 || (i ^ j) > c[len - pos + 1],
				(!i) && lead1, (!j) && lead2, limit1 && i == top1, limit2 && j == top2);
		}
	}
	dp[pos][st1][st2][lead1][lead2][limit1][limit2] = res;//记忆化
	return res;
}
AC
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long LL;
const int maxn = 35;
int len, a[maxn], b[maxn], c[maxn];
//x&y<=C   &&   x|y>=c
LL dp[maxn][2][2][2][2][2][2];
LL dfs(int pos, bool st1, bool st2, bool lead1, bool lead2, bool limit1, bool limit2) {
	if (pos > len) return (!lead1) && (!lead2);
	if (dp[pos][st1][st2][lead1][lead2][limit1][limit2] != -1)
		return dp[pos][st1][st2][lead1][lead2][limit1][limit2];
	if (st1 && st2 && !limit1 && !limit2 && !lead1 && !lead2)
		return (LL(1) << (len - pos + 1)) * (1 << (len - pos + 1));
	LL res = 0;
	int top1 = limit1 ? a[len - pos + 1] : 1;
	int top2 = limit2 ? b[len - pos + 1] : 1;
	for (int i = 0; i <= top1; i++) {
		for (int j = 0; j <= top2; j++) {
			if (!st1 && (i & j) > c[len - pos + 1]) continue;
			if (!st2 && (i ^ j) < c[len - pos + 1]) continue;
			res += dfs(pos + 1, st1 || (i & j) < c[len - pos + 1], st2 || (i ^ j) > c[len - pos + 1],
				(!i) && lead1, (!j) && lead2, limit1 && i == top1, limit2 && j == top2);
		}
	}
	dp[pos][st1][st2][lead1][lead2][limit1][limit2] = res;
	return res;
}
LL part(int x, int y, int z) {
	memset(a, 0, sizeof(a));
	memset(b, 0, sizeof(b));
	memset(c, 0, sizeof(c));
	memset(dp, -1, sizeof(dp));
	LL res = LL(x) * y;
	int l1, l2, l3; l1 = l2 = l3 = 0;
	while (x)a[++l1] = x & 1, x >>= 1;
	while (y)b[++l2] = y & 1, y >>= 1;
	while (z)c[++l3] = z & 1, z >>= 1;
	len = max(l1, max(l2, l3));
	return res - dfs(1, 0, 0, 1, 1, 1, 1);
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int t, a, b, c; cin >> t;
	while (t--) {
		cin >> a >> b >> c;
		cout << part(a, b, c) << '\n';
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值