2019牛客暑期多校训练营(第七场)H-Pair (数位dp)

题意:

链接:https://ac.nowcoder.com/acm/contest/887/H

给你三个数 A、B、 C, 令 x ∈[ 1, A ],y ∈ [ 1, B ] ,至少符合 x & y > C 和 x ^ y < C 其中的一个条件,问你有多少个符合条件的 (x, y) 。

解题思路:

首先看他问的至少符合 x & y > C 和 x ^ y < C 这两个条件之一,也可以全符合,如果分开dp,求出来的答案还需要去重,非常麻烦,于是可以考虑其反面,即:求符合 x & y <= C 且 x ^ y >= C 的(x, y)的个数为sum ,然后用 A * B - sum 就是答案了。
构造可以描述以上状态的dp结构,首先需要当前位置即pos, 然后因为需要枚举每一位,每一位需要上限的控制,对于A和B都需要控制,即:limit_a, limit_b 来表示是否是上界,最后需要表示当前的状态是 x & y < C 还是 x & y == C ,用 and_ 来表示,and_ 是 1 说明当前状态是  x & y == C 还需要进一步的判断 ,and_ 是 0 说明当前状态是  x & y < C ,以后就可以不用判断了,肯定都满足 x & y < C 了,因为高位满足了 小于 C,那么就不用管低位了,肯定是小于C的。表示当前的状态是 x ^ y > C 还是 x ^ y == C,同理,使用 xor_ 来表示。所以最后的数位dp的状态描述为 : dp [ pos ] [ limit_a ] [ limit_b ] [ and_ ] [ xor_ ] 。其余的就是数位dp的模板了。还有一点需要注意当 x == 0 && y > C 时 或者 x > C && y == 0 时 : x & y < C 。 又因为 x >= 1 && y >= 1 所以最后的答案需要减去多算的 max( 0, B - C + 1 ) + max( 0, A - C + 1 ) 。
还有一个需要注意的是这里保存状态的时候没有判断 limit , 一方面这个题目每次输入都需要把dp清成-1,不用担心算一些边界的状态让以后的其他样例给使用了。另一方面这个题目保存的状态很多,没有冲突的状态,这里举一个有冲突的状态的例子:

/**********************************************************************
    // 一些有限制是不可以记录状态的,因为此时的状态和无限制时候的状态不一致
    // 比如: 假设限制是 不可以连续出现两个 1 ,如: 11 12211 211 这些
    // 数字都是不合法的求 1 - 521 有多少合法的数字: dp[ pos ][ pre ] 代表
    // 当前位置是pos,前一位置的数是 pre ,的合法状态。假设百位现在枚举到 0 ,
    // 现在limit == 0,是无限制的, 然后十位枚举到2,个位枚举到 1,会产生
    // 一个状态dp[1][2],并且容易计算出dp[1][2] = 10(因为位置为 1 的(个位数)前一个数字是2,
    // 所以10个数字 0 - 9 都合法),这个状态会被记录下来,继续dfs,一直到百位是5,十位是2时候,
    // 此刻就会递归到了pos = 1,pre = 2 的这一层,然而dp[ 1 ][ 2 ]的状态是10,
    // 如果不判断limit,程序直接返回状态dp[ 1 ][ 2 ],显然是不对的,因为现在百位和十位都在上限了,
    // 所以当前pos == 1的位置只能取 [ 0、1 ]这两个数字不会产生 9 个合法的数,
    // 这时候就产生了状态冲突问题,所以当limit有限制的时候不理他,直接算,因为这样的状态并不多。
    *******************************************************************************/

还不太明白可以参考代码,有详细注释。

AC代码:

#include<bits/stdc++.h>
#define up(i, x, y) for(ll i = x; i <= y; i++)
#define down(i, x, y) for(ll i = x; i >= y; i--)
#define bug prllf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
typedef long long ll;
typedef unsigned long long ull;
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll maxn = 1e5 + 7;
const double pi = acos(-1);
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
using namespace std;

ll A, B, C;
ll dp[32][2][2][2][2];

ll dfs(int pos, int limit_a, int limit_b, int and_, int xor_) 
// and_ 代表是否满足 A & B < C 是的话为 0 ,否则为 1 , xor_  代表是否满足 A ^ B > C 是的话为 0 ,否则为 1
{
    if(pos < 0) return 1LL;
    if(dp[pos][limit_a][limit_b][and_][xor_] != -1) return dp[pos][limit_a][limit_b][and_][xor_];
    int topa = 1, topb = 1, tand = 1, txor = 0;  // ①位置
    if(limit_a) topa = (A >> pos) & 1;
    if(limit_b) topb = (B >> pos) & 1;
    if(and_) tand = (C >> pos) & 1;  // 三位置
    if(xor_) txor = (C >> pos) & 1;
    ll sum = 0;
    for(ll i = 0; i <= topa; i++)
    {
        for(ll j = 0; j <= topb; j++)
        {
            if( (i & j) > tand ) continue; // ②位置
            if( (i ^ j) < txor ) continue;
            sum += dfs(pos - 1, limit_a && i == topa, limit_b && j == topb, and_ && ((i & j) == tand), xor_ && ((i ^ j) == txor));

//   pos - 1 :
//   位置找下一个位置

//   limit_a && i == topa :
//   如果之前的位的已经到达上界(limit_a == 1) 现在的也到达上界(i==topa) 那么接下来的也是到达上界

//   limit_b && j == topb : 同上

//   and_ && ((i & j) == tand) : 
//   如果 i & j < tand 那么会传入 0 ,因为这一位 i & j < tand 所以以后的位不管是0还是1,A & B 总是会  < C, 
//   所以在 ①位置 那里会赋初值 tand = 1,如果and_ 为 0,说明以后的都会符合,所以在 ②位置 的判断时候都会满足条件
//   如果 I & j == tand 那么说明当前还不能判断,所以在 三位置 会取出真实值来进行进一步的判断

//   xor_ && ((i ^ j) == txor) : 同上

        }
    }
    return dp[pos][limit_a][limit_b][and_][xor_] = sum;
}

int main()
{
    ll T; scanf("%lld", &T); while(T--)
    {
        memset(dp, -1, sizeof(dp));
        scanf("%lld %lld %lld", &A, &B, &C);
        ll ans = dfs(30, 1, 1, 1, 1);
        ans -= max(0LL, B - C + 1);
        ans -= max(0LL, A - C + 1);
        printf("%lld\n", A * B - ans);
    }
}

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值