To argue, or not to argue [from vjudge] 题解
一.题意:
一个\(n\times m\)的矩阵,表示可用的座位,有\(k\)对人不能坐在一起(每个人均不同),同时有些座位被其他客人占了,这\(k\)对人不能选这类位置坐,问合法的方案数\((n\times m\leq144)\)。
二.思路分析:
这个\(144\)给的太特别了,很好注意到他是\(12\)的平方,也就等价告诉你,\(min\{n,m\}\leq12\),那其实这样的话第一反应肯定会倾向于往状压靠,但你发现其实还是没那么容易定义状态,所以我们先试着分析其他性质。
再次分析题目,发现不能坐在一起这个条件实在是太难处理了,正难则反,我们考虑能坐在一起的方案,最终所求即为\(0\)对人坐在一起,额,这个很难不往容斥的思路走,我们定义\(G(i)\)表示钦定至少\(i\)对人放在一起的方案数,但是要注意的是,此处的\(i\)对人是不做区分的,也就是并不是题目中所说的每个人互不相同,所以说对于每次的贡献我们理应有:
\]
上面那个冗长一段的函数\(p\)是排列数,而\(cnt\)是被普通客人占了的位置。
现在给出组合解释:因为我们的方案数是互不区分的,但是实际上我们有\(k\)对不同的人,所以我们要先从其中选\(i\)对不同的出来。其次,因为我们有区分,所以这\(i\)对人的顺序是可以进行排列的,每一次排列都是不同的方案,注意,这里是组与组之间进行排列。现在考虑每对内部,发现每对内部的位置可以互相交换且对于全局独立,所以有\(2^i\)种方案,最后,剩下的数可以在所有的剩余合法位置任意排列,就有了我们冗长的排列数。
现在我们令刚才的贡献为\(now_i\)
所以最终的答案就为:
\]
现在的问题就在于如何求每一个\(G\),考虑dp,其实已经有很多部分在引导我们往正确的状态上定义了,首先是\(min\{n,m\}\leq12\)这一条件中的最小值这部分,就提醒我们这可能不是一般的状态压缩dp。其次,你发现\(G\)的定义中有\(i\)对人相邻且每对不做区分这一条件就很像经典多米诺骨牌摆放问题,所以大胆猜想可以使用轮廓线dp解决这个问题,定义:\(f(i,S,k)\)表示考虑到第\(i(1\leq i\leq n\times m)\)号点时,轮廓线状态为\(S\),且放置了\(k\)个骨牌的方案数。
那就有如下转移:
1. 当前点已经被覆盖,那么我们什么都做不了,所以:
\]
2. 当前点未被覆盖:
\((1)\):我们横着放,此时,需要考虑右边格子的状态,若为空才能放:
\]
\((2)\):竖放,与上述分析同理:
\]
\((3)\):需要注意的是,这个问题于经典的骨牌放置是有区别的,我们不需要把矩阵放满,所以此时可以选择不放:
\]
上述便是所有的转移了。
在最终实现时,需要注意:轮廓线dp虽然支持复杂度降到\(O(nmk\cdot 2^{min\{n,m\}})\)
但是在这个问题中,取最小值是有条件的,如果最开始你发现\(m>n\)那么就需要将矩阵的行和列调转再进行操作。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
const int mod = 1e9 + 7,NM = 150,S = 4096 + 5,K = 75,MAXF = 1000 + 5;
int n,m,k,f[NM][S][K],ans,sum_get,id;
char s[NM][NM],ts[NM][NM];
int inv[MAXF],fac[MAXF],pow_2[MAXF];
int qpow(int a,int b){
int res = 1;
while(b){
if(b & 1) res = (res * a) % mod;
a = (a * a) % mod;
b >>= 1;
}
return res;
}
int C(int a,int b){
if(b > a || b < 0 || a < 0) return 0;
return fac[a] * inv[b] % mod * inv[a - b] % mod;;
}
int A(int a,int b){
return C(a,b) * fac[b] % mod;
}
void init(){
pow_2[0] = fac[0] = 1;
for(int i = 1;i <= 200;++i) pow_2[i] = pow_2[i - 1] * 2 % mod;
for(int i = 1;i <= 200;++i) fac[i] = fac[i - 1] * i % mod;
inv[200] = qpow(fac[200],mod - 2);
for(int i = 200 - 1;i >= 0;--i)
{
inv[i] = inv[i + 1] * (i + 1) % mod;
}
}
void calc_dp(){
f[0][0][0] = 1;
id = 0;
for(int i = 0;i < n;++i){
for(int j = 0;j < m;++j){
++id;
for(int p = k;p >= 0;--p){
for(int Set = 0;Set < (1 << m);++Set){
if(f[id - 1][Set][p]){
if(Set >> j & 1){//这个点已经被覆盖
f[id][Set - (1 << j)][p] += f[id - 1][Set][p];
f[id][Set - (1 << j)][p] %= mod;
}else{//未被覆盖
if(p < k && j < m - 1 && ((Set >> (j + 1)) & 1) == 0 && s[i + 1][j + 1] == '.' && s[i + 1][j + 2] == '.'){//横放的条件
f[id][Set + (1 << (j + 1))][p + 1] += f[id - 1][Set][p];
f[id][Set + (1 << (j + 1))][p + 1] %= mod;
}
if(p < k && i < n - 1 && s[i + 1][j + 1] == '.' && s[i + 2][j + 1] == '.'){//竖放的条件
f[id][Set + (1 << j)][p + 1] += f[id - 1][Set][p];
f[id][Set + (1 << j)][p + 1] %= mod;
}
//这里可以不放,因为题目不要求填满整个网格图
f[id][Set][p] += f[id - 1][Set][p];
f[id][Set][p] %= mod;
}
}
}
}
}
}
}
void calc_ans(){
for(int i = 0;i <= k;++i){
if(i % 2 == 1){
ans = (ans - (f[id][0][i] * C(k,i) % mod * fac[i] % mod * pow_2[i] % mod * A(n * m - sum_get - 2 * i,2 * (k - i)) % mod) + mod) % mod;
}else{
ans = (ans + f[id][0][i] * C(k,i) % mod * fac[i] % mod * pow_2[i] % mod * A(n * m - sum_get - 2 * i,2 * (k - i)) % mod) % mod;
}
}
}
void clear(){
for(int i = 0;i <= n * m;++i)
for(int _s = 0;_s < (1 << m);++_s)
for(int p = 0;p <= k;++p)
f[i][_s][p] = 0;
ans = 0;
sum_get = 0;//没有重置这个浪费了好多时间去找问题
}
signed main(){
int T;
scanf("%lld",&T);
init();
while(T--){
scanf("%lld%lld%lld",&n,&m,&k);
for(int i = 1;i <= n;++i) scanf("%s",s[i] + 1);
if(m > n){
for(int i = 1;i <= m;++i){
for(int j = 1;j <= n;++j){
ts[i][j] = s[j][i];
}
}
std::swap(n,m);
for(int i = 1;i <= n;++i){
for(int j = 1;j <= m;++j){
s[i][j] = ts[i][j];
}
}
}
for(int i = 1;i <= n;++i){
for(int j = 1;j <= m;++j){
sum_get += (s[i][j] != '.');
}
}
//上面的代码是初始化与预处理
calc_dp();
calc_ans();
printf("%lld\n",ans);
//重置变量
clear();
}
return 0;
}
To argue, or not to argue [from vjudge] 题解的更多相关文章
- Cobalt Strike使用教程一
Cobalt Strike使用教程一 0x00 简介 Cobalt Strike是一款基于java的渗透测试神器,常被业界人称为CS神器.自3.0以后已经不在使用Metasploit框架而作为 ...
- 【POJ1151】Atlantis(线段树,扫描线)
[POJ1151]Atlantis(线段树,扫描线) 题面 Vjudge 题解 学一学扫描线 其实很简单啦 这道题目要求的就是若干矩形的面积和 把扫描线平行于某个轴扫过去(我选的平行\(y\)轴扫) ...
- 【UVa11426】GCD - Extreme (II)(莫比乌斯反演)
[UVa11426]GCD - Extreme (II)(莫比乌斯反演) 题面 Vjudge 题解 这.. 直接套路的莫比乌斯反演 我连式子都不想写了 默认推到这里把.. 然后把\(ans\)写一下 ...
- 【POJ1704】Georgia and Bob(博弈论)
[POJ1704]Georgia and Bob(博弈论) 题面 POJ Vjudge 题解 这种一列格子中移动棋子的问题一般可以看做成一个阶梯博弈. 将一个棋子向左移动时,它和前面棋子的距离变小,和 ...
- 【BZOJ1814】Ural 1519 Formula 1 (插头dp)
[BZOJ1814]Ural 1519 Formula 1 (插头dp) 题面 BZOJ Vjudge 题解 戳这里 上面那个链接里面写的非常好啦. 然后说几个点吧. 首先是关于为什么只需要考虑三进制 ...
- HDU3045 Picnic Cows
题面 HDU vjudge 题解 将权值排序,则分组一定是连续的 设$f[i]$表示前$i$头牛的最小代价,则($a[i]$为$i$的权值): $$ f[i] = f[j - 1] + sum[i] ...
- 【HDU5469】Antonidas(点分治,字符串哈希)
[HDU5469]Antonidas(点分治,字符串哈希) 题面 HDU Vjudge 题解 啊哈?什么垃圾一眼点分治+Hash判断,哈哈哈哈哈,让我来码码码. 诶,怎么WA了.改改改改改. 诶,怎么 ...
- 【HDU4336】Card Collector(Min-Max容斥)
[HDU4336]Card Collector(Min-Max容斥) 题面 Vjudge 题解 原来似乎写过一种状压的做法,然后空间复杂度很不优秀. 今天来补一种神奇的方法. 给定集合\(S\),设\ ...
- 【POJ2411】Mondriaan's Dream(轮廓线DP)
[POJ2411]Mondriaan's Dream(轮廓线DP) 题面 Vjudge 题解 这题我会大力状压!!! 时间复杂度大概是\(O(2^{2n}n^2)\),设\(f[i][S]\)表示当前 ...
- 【SPOJ】QTREE7(Link-Cut Tree)
[SPOJ]QTREE7(Link-Cut Tree) 题面 洛谷 Vjudge 题解 和QTREE6的本质是一样的:维护同色联通块 那么,QTREE6同理,对于两种颜色分别维护一棵\(LCT\) 每 ...
随机推荐
- 基于预生成 QA 对的 RAG 知识库解决方案
核心价值 QA 预生成技术 采用创新的问答对生成方法,相比传统文本切片技术,能够更精准的构建知识库,显著提升检索与问答效果. 企业级场景验证 已在真实业务场景中落地应用,实现从传统搜索到智能搜索的无缝 ...
- WindowsPE文件格式入门05.PE加载器LoadPE
https://bpsend.net/thread-316-1-1.html LoadPE - pe 加载器 壳的前身 如果想访问一个程序运行起来的内存,一种方法就是跨进程读写内存,但是跨进程读写内存 ...
- golang unsafe遇上字符串拼接优化导致的bug
最近料理老项目的时候被unsafe坑惨了,这里挑一个最不易察觉的错误记录一下. 这个问题几乎影响近几年来所有的go版本,为了方便讨论我就用最新版的1.24.3做例子了. 线上BUG 我们有一个收集集群 ...
- 创建Spring Boot项目时,提示 Cannot download 'https://start.spring.io'
问题提出 在使用IDEA创建Spring Boot项目时,提示无法连接https://start.spring.io,内容如下: Cannot download 'https://start.spri ...
- 《刚刚问世》系列初窥篇-Java+Playwright自动化测试-21- 操作鼠标拖拽 - 中篇(详细教程)
1.简介 上一篇中,主要是介绍了拖拽的各种方法的理论知识以及实践,今天宏哥讲解和分享一下划取字段操作.例如:需要在一堆log字符中随机划取一段文字,然后右键选择摘取功能. 2.划取字段操作 划取字段操 ...
- DBA备库工具:Oracle环境中表空间全自动扩容
我们的文章会在微信公众号IT民工的龙马人生和博客网站( www.htz.pw )同步更新 ,欢迎关注收藏,也欢迎大家转载,但是请在文章开始地方标注文章出处,谢谢! 由于博客中有大量代码,通过页面浏览效 ...
- TypeScript(TS)JavaScript(JS)中的所有循环方法
for循环: for (let i = 0; i < array.length; i++) { // 循环体 } for-of循环: for (const element of array) { ...
- 自定义Excel右下角状态栏的显示项
Excel右下角的状态栏是一个非常方便和有用的显示栏,在执行一些简单的计算时可以直接选取需要计算的区域即可得到汇总,计数,平均值等结果. 如果需要调整状态栏的显示选项,则在状态栏处右键点击: 可以选中 ...
- 放弃Cursor,拥抱Claude code(白嫖100美金余额,可以用Claude Sonnet 4)
前言 之前一直在使用Cursor,但是最近Cursor一直偷偷改价降智,不是那么好用了,Claude的公司Anthropic自己推出AI编程工具Claude code体验了一下,感觉非常的丝滑,主要是 ...
- taro从0开发一个组件插件包
前言 经常看到taro物料市场 上有很多好用的组件,因此我也想造轮子. 网上有很多现成的方案,可是我觉得不好,他们都是在一个完整的项目中开发依赖的,实际上我们可能不需要这么多无用的依赖和代码. 所以我 ...