//加法原理
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const ll inf=2e18,p=998244353;
ll dp[103][(1<<11)+5];
//dp[i][j]表示到第i列为止,且最后一列状态为j的方案
//最后一列状态为j对应题目中“第k和第k+1个同时亮”
// 最后调用f函数返回值来检查是否有k个灯亮
int lowbit(int x)//提取最低位的1
{
return x & -x;
/* 提取0110(6)中的最低位的1为例:
-6:0110 (6的二进制)
0101(-1)
1010取反得到-6的二进制表示
与运算
1010
0110
=0010
*/
}
// 返回x中1的数量
int f(ll x)
{
int res=0;
while(x) //重复过程到x变为0,res存储x二进制中1的个数
{
res++;
x-=lowbit(x);//将x最低位的1清零
// 理解每次清除一个1变为0,
// 消去的位置是从右向左(由低到高);
}
return res ;
}
int main()
{
int n,m,k;cin>>n>>m>>k;
for(int sta=0;sta<(1<<n);sta++)dp[1][sta]=1;
//dp[i][j]表示到第i列为止,且该列状态为j的方案
//这里的状态是指的是001 010 011 100 101 110 111这样的状态
// 所以第二个维度的划分标准并不是亮了几盏灯,而是具体的亮灯组合
for(int i=2;i<=m;i++)//从第二个开始遍历所有人家
{
//当前列的状态:全灭到全亮
for(int sta=0;sta<(1<<n);sta++)
{
if(f(sta)<k)continue;//如果当前列的灯不足k个直接不考虑前一列的情况;
for(int lst_sta=0;lst_sta<(1<<n);lst_sta++)
{
if(f(sta&lst_sta)>=k)//两列中同时亮的灯的个数大于k
//对于一个sta,且假设第i位是否有1来说,sta第i位可以有1也可以没有1,只是需要关注最后与运算的结果是否有1
//与运算合法要求:结果中有k个1就行,情况有很多种k(k可以在所有村庄任选k个相邻或不相邻...只要有k个就行)
{//同时亮体现在与运算中的情况就是相同位置运算的结果就是1;
dp[i][sta]+=dp[i-1][lst_sta]%p;
//dp[i][sta]:对于第i个位置亮灯情况sta(如101:表示第1 3 个村的灯亮)
//由前一个位置所有合法状态的方案之和(合法:lst_sta和sta与运算至少存在k个1)
//进一步解释:已知当前的sta和lst_sta满足条件,dp[i][sta]的方案取决于你有多少种dp[i][lst]
//然后因为是遍历所有的lst_sta,所以存在多种lst_sta与sta合法
//即对于一个sta能够找到多个lst_sta使得两者与运算之后合法
//状态转移就是加法原理,
//考虑所有的情况:对于同时亮K盏灯的情况在第1,2,3,...m个位置合法的方案总共有多少
// 最后将结果存储到dp[m][sta]输出就是了
//累加一种情况you
//k的约束体现在状态转移上面
}
}
/*
对于每一个位置,等到sta>=k的时候考虑前一列的情况
两列同时亮灯的数量大于k
两层循环的意思:对于第j列的每一个状态,都会检查第j-1的全部可能状态,这样就会找到符合条件的
dp数组值是非负整数
对于第i列,给定一个k,k个1可以出现在不同的位置,即合法于(f(sta)<k)有多个
*/
}
}
ll ans=0;
// 即虽然最后状态没有体现k,但是所有状态在转移的时候满足k的约束了
for(int sta=0;sta<(1<<n);sta++)
{
ans=(ans+dp[m][sta])%p;
//最后dp[m][sta]是所有情况的加和,
// 因为对于所有情况都可能有前一种方案使得两者与运算之后满足同时亮k盏灯的条件
}
cout<<ans<<endl;
return 0;
}
再遇白骨精
最新推荐文章于 2025-07-05 01:17:14 发布