[DTOI 2023] D. Goodbye 2022(bitset用法+数学+思维)

108 篇文章 0 订阅

[DTOI 2023] D. Goodbye 2022

题目背景

我用烟花宣告,用挥手告别,用鞠躬感谢,过去的都已经过去,接下来的路我要悠闲地走,愉悦地走,脚步如同时间不会停止,下一年,我们还会再会。

题目描述

这次的题目背景和 luanmenglei 没有一点关系。

给定 n , k , p n,k,p n,k,p,求有多少有序 p p p 元组 ( a 1 , a 2 , ⋯   , a p ) (a_1,a_2,\cdots,a_p) (a1,a2,,ap) 满足

  • ∀ i ∈ [ 1 , p ] \forall i \in [1,p] i[1,p] a i ∈ [ 1 , n ] a_i\in [1,n] ai[1,n]

  • ∀ i ∈ [ 1 , p ) \forall i\in [1,p) i[1,p) popcount ⁡ ( a i ⊕ a i + 1 ) = k \operatorname{popcount}(a_i\oplus a_{i+1})=k popcount(aiai+1)=k

  • ∀ i , j ∈ [ 1 , p ] , i ≠ j \forall i,j\in[1,p],i\neq j i,j[1,p],i=j a i ≠ a j a_i\neq a_j ai=aj

答案对 998244353 998244353 998244353 取模。


  • 其中 popcount ⁡ ( x ) \operatorname{popcount}(x) popcount(x) 表示 x x x 在二进制表达下 1 1 1 的个数。
  • ⊕ \oplus 表示按位异或操作。
  • 两个有序 p p p 元组 ( a 1 , a 2 , … , a p ) (a_1,a_2,\dots,a_p) (a1,a2,,ap) ( b 1 , b 2 , … , b p ) (b_1,b_2,\dots,b_p) (b1,b2,,bp) 不同当且仅当存在 i ∈ [ 1 , p ] i\in[1,p] i[1,p] 使得 a i ≠ b i a_i\neq b_i ai=bi

输入格式

一行三个正整数 n , k , p n,k,p n,k,p

输出格式

一行一个数,表示答案。

样例 #1

样例输入 #1

5 1 2

样例输出 #1

8

样例 #2

样例输入 #2

6 1 3

样例输出 #2

12

样例 #3

样例输入 #3

7 1 4

样例输出 #3

48

样例 #4

样例输入 #4

8 3 5

样例输出 #4

6

样例 #5

样例输入 #5

9 2 5

样例输出 #5

72

样例 #6

样例输入 #6

114 3 3

样例输出 #6

106624

样例 #7

样例输入 #7

514 3 4

样例输出 #7

296097032

样例 #8

样例输入 #8

1000 7 5

样例输出 #8

569405945

样例 #9

样例输入 #9

1000 7 1

样例输出 #9

1000

提示

对于所有测试数据,保证 1 ≤ n ≤ 1000 1\leq n \leq 1000 1n1000 1 ≤ k ≤ ⌊ log ⁡ 2 n ⌋ 1\leq k\leq \lfloor \log_2 n\rfloor 1klog2n 1 ≤ p ≤ 5 1 \leq p \leq 5 1p5

每个测试点的具体限制见下表:

测试点编号 n ≤ n\leq n p = p = p=
1 1 1 1000 1000 1000 1 1 1
2 ∼ 3 2 \sim 3 23 1000 1000 1000 2 2 2
4 ∼ 5 4 \sim 5 45 300 300 300 3 3 3
6 ∼ 12 6 \sim 12 612 1000 1000 1000 3 3 3
13 ∼ 15 13 \sim 15 1315 1000 1000 1000 4 4 4
16 ∼ 21 16 \sim 21 1621 300 300 300 5 5 5
22 ∼ 25 22 \sim 25 2225 1000 1000 1000 5 5 5

思路

  • 首先,这个条件: ∀ i ∈ [ 1 , p ) \forall i\in [1,p) i[1,p) popcount ⁡ ( a i ⊕ a i + 1 ) = k \operatorname{popcount}(a_i\oplus a_{i+1})=k popcount(aiai+1)=k。这个我们可以用C++的 __builtin_popcount(i^j)==k来维护,它可以在 O ( 1 ) O(1) O(1) 的时间复杂度算出来。
  • 其次, 1 ≤ p ≤ 5 1 \leq p \leq 5 1p5,说明我们就可以分类讨论即可。

在这里插入图片描述
在这里插入图片描述

本人的遇到的问题

  • 我们要特别注意,第二个条件是: ∀ i ∈ [ 1 , p ) \forall i\in [1,p) i[1,p),没有包括 p p p
  • 图片里面的 f [ a 1 ] f[a_1] f[a1] 算的就是 a 2 a_2 a2 的合法数量。(我个人认为因为有条件 1 1 1 的限制,再加上条件 3 3 3 的限制,所以 f [ a 1 ] f[a_1] f[a1] 算的数是 a 2 a_2 a2 的数,因为此时的 a 1 a_1 a1 要满足条件 2 2 2,所以 a 2 a_2 a2 就只能跟 f [ a 1 ] f[a_1] f[a1] 相等)
  • 我感觉 f [ a x ] f[a_x] f[ax] 来算不固定枚举的 a a a 的种类数,由于条件 2 2 2 ,所以它的周围(比如图片中 p = 3 p=3 p=3 时,枚举 a 2 a_2 a2 ,那么因为条件 2 2 2,此时 a 1 , a 3 a_1,a_3 a1,a3 的方案数就会受它影响;再比如图片中的 p = 5 p=5 p=5 时的第一点,它要算 a 1 a_1 a1 ,此时 a 1 a_1 a1 是受 a 2 a_2 a2 的枚举影响,但它为什么要减去 a 3 , a 4 a_3,a_4 a3,a4 ,因为当满足 b [ a 2 ] [ a 4 ] = 1 b[a_2][a_4]=1 b[a2][a4]=1 的时候,此时 a 1 a_1 a1 绝对不能等于 a 4 a_4 a4,因为你如果相等的话,那么此时 a 1 ⊕ a 2 = 0 a_1\oplus a_2=0 a1a2=0,此时就不满足题意了(具体的可以看第一张图,因为它的不等关系比第二张更清晰))
  • 图片中说的枚举 a x a_x ax ,所以其他的值都得按照它们来。
  • 图片中所说 b [ a 2 ] [ a 3 ] = 1 b[a_2][a_3]=1 b[a2][a3]=1,此时说明满足第二个条件,等于 0 0 0 则不满足。
  • 图片中的 f [ i ] f[i] f[i] 怎么算呢,我们可以用 bitset 的 count() 函数来算(它算的是 1 的总个数)。
  • 为什么 b [ a 2 ] [ a 4 ] b[a_2][a_4] b[a2][a4]能分类讨论呢?因为 a 2 a_2 a2 a 4 a_4 a4 不是相邻的。

代码

//当p=1时,条件2、3无用
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N = 1010,mod = 998244353;

bitset<N> s[N];
int n,k,p;
int f[N];
int b[N][N];
int ans;

signed main(){
    cin>>n>>k>>p;
    
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i!=j)
            s[i][j]=(__builtin_popcount(i^j)==k);//能在O(1)找到这个条件所有情况。满足就为1
        }
    }
    
    //当p=1时,说明只有一个数,所以答案为n
    
    if(p==1){
        cout<<n<<endl;
    }else if(p==2){
        //p=2时,2个数,通过枚举a1来约束a2
        for(int i=1;i<=n;i++){
            ans=(ans+s[i].count())%mod;
        }
        cout<<ans;

    }else if(p==3){
        //p=3时,枚举合法的a2,因为a1!=a3,所以它们总的方案数为p2*p2-p2
        //ans=p2(p2-1)
        
        for(int i=1;i<=n;i++){
            ans=(ans+s[i].count()*(s[i].count()-1))%mod;
        }
        cout<<ans;
    }else if(p==4){
        //p=4,我们枚举a2、a3,这样就不担心连锁反应了
        
        int t;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(s[i][j]){
                    t=(s[i].count()-1)*(s[j].count()-1)%mod;
                    t=(t-(s[i]&s[j]).count()+mod)%mod;//因为这a1和a4很可能是相等的。
                    ans=(ans+t)%mod;
                }
            }
        }
        
        cout<<ans;
    }else{
        //p=5,我们枚举a2和a4,此时a3有a2^a4种选法
        int t1,t2;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i!=j){
                    ans=(ans+((s[i].count()-1-s[i][j])*(s[j].count()-1-s[i][j])-(s[i]&s[j]).count()+1)*((s[i]&s[j]).count()))%mod;
                }
            }
        }
        
        cout<<ans;
        
    }
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

green qwq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值