Codeforces 题目传送门 & 洛谷题目传送门

qwq 这题大约是二十来天前 AC 的罢,为何拖到此时才完成这篇题解,由此可见我是个名副其实的大鸽子(

这是我上 M 的那场我没切掉的 F2 哦,u1s1 我上 M 还要多亏那场的 F1 啊(

首先暴力 \(3^nn\) 枚举子集显然是过不去的,否则这题就退化到 F1 了(大雾

考虑用一个容斥的思想,考虑求出答案的后缀和 \(f_i\),其中 \(i\) 是一个长度为 \(n-1\) 的二进制串。形式化地说,\(f_i\) 等于满足以下条件的排列 \(p\) 的个数:如果 \(i\) 的第 \(j\) 位为 \(1\) 那么 \(p_j,p_{j+1}\) 必须认识,否则 \(p_j,p_{j+1}\) 可以认识可以不认识。显然求出 \(f_i\) 之后再做一遍高维差分——或者你叫它 IFWTand 我也没意见,即可求出真正的 \(ans_i\)。

接下来考虑怎样求出 \(f_i\),首先我们将 \(i\) 用二进制表示,显然 \(i\) 的二进制表示可以划分成若干个连续的 \(1\) 组成的段,假设每一段的长度分别为 \(l_1,l_2,\cdots,l_m\),那么这个划分方案数就等价于将全集 \(U=\{1,2,\cdots,n\}\) 划分为 \(m\) 个集合 \(S_1,S_2,\cdots,S_m\),满足 \(|S_i|=l_i\),再将 \(S_i\) 中的所有人排成一列,满足相邻的人必须互相认识的方案数。注意到题目中涉及”对于集合 \(S\),将 \(S\) 中的人排成一列,满足相邻的人必须互相认识的方案数 \(g_S\)“的子问题,这个是非常容易求得的,简单设个 \(dp_{S,i}\) 表示将 \(S\) 中的人排成一列,最后一个人为 \(i\) 并且相邻的人互相认识的方案数,这样即可在 \(2^nn^2\) 的时间内求出 \(g_S\),这样一来 \(f_i=\sum\limits_{|S_i|=l_i,S_1\cup S_2\cdots\cup S_m=U}\prod\limits_{i=1}^mg_{S_i}\),这个看起来有点像子集卷积,我们再将原来的一维数组 \(g_S\) 扩充成二维数组 \(h_{i,S}\),\(h_{i,S}=\begin{cases}g_S&(|S|=i)\\0&(|S|\ne i)\end{cases}\),再将所有 \(h_{l_i}\) FWTor 卷起来得到集合幂级数 \(F\),最终的 \(F_U\) 即为 \(f_i\)。

有人可能会说,你对每个 \(f_i\) 都搞一遍 FWTor,这样不就变成 \(4^nn\) 了吗?甚至跑不过 \(3^nn\) 的暴力。事实上注意到段与段之间的顺序是不重要的,而 \(18\) 的整数划分总共只有 \(385\) 种,因此我们可以对每个整数划分都求一遍答案,求 \(f_i\) 时直接调用刚刚求得的值即可。

时间复杂度大约是 \(\mathcal O(385·2^n+2^nn^2)\)

const int MAXP=1<<18;
const int MAXN=18;
int n;ll dp[MAXP+5][MAXN+5],g[MAXN+5][MAXP+5];
char s[MAXN+5][MAXN+5];
void FWTor(ll *a,int len,int type){
for(int i=2;i<=len;i<<=1)
for(int j=0;j<len;j+=i)
for(int k=0;k<(i>>1);k++)
a[(i>>1)+j+k]+=a[j+k]*type;
}
void FWTand(ll *a,int len,int type){
for(int i=2;i<=len;i<<=1)
for(int j=0;j<len;j+=i)
for(int k=0;k<(i>>1);k++)
a[j+k]+=a[(i>>1)+j+k]*type;
}
map<vector<int>,ll> ret;
vector<int> cur;
ll mul[MAXN+5][MAXP+5],ans[MAXP+5];
void dfs(int x,int lst){
if(x==n+1){
ll sum=0;
for(int i=0;i<(1<<n);i++){
int dig=bitcnt(((1<<n)-1)^i)&1;
if(dig) sum-=mul[cur.size()][i];
else sum+=mul[cur.size()][i];
} ret[cur]=sum;
// for(int x:cur) printf("%d ",x);printf("%lld\n",sum);
return;
}
for(int i=lst;i<=n-x+1;i++){
cur.pb(i);
for(int j=0;j<(1<<n);j++) mul[cur.size()][j]=mul[cur.size()-1][j]*g[i][j];
dfs(x+i,i);cur.pop_back();
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
for(int i=1;i<=n;i++) dp[1<<i-1][i]=1;
for(int i=1;i<(1<<n);i++) for(int j=1;j<=n;j++) if(i>>(j-1)&1){
for(int k=1;k<=n;k++) if((i>>(k-1)&1)&&j!=k&&s[k][j]=='1'){
dp[i][j]+=dp[i^(1<<j-1)][k];
} g[bitcnt(i)][i]+=dp[i][j];
}
// for(int i=1;i<(1<<n);i++) printf("%lld\n",g[bitcnt(i)][i]);
for(int i=1;i<=n;i++) FWTor(g[i],1<<n,1);
for(int i=0;i<(1<<n);i++) mul[0][i]=1;dfs(1,1);
for(int i=0;i<(1<<n-1);i++){
vector<int> v;int pre=-1;
for(int j=0;j<n-1;j++) if(~i>>j&1){
v.pb(j-pre);pre=j;
} v.pb(n-1-pre);sort(v.begin(),v.end());
// for(int x:v) printf("%d ",x);printf("\n");
ans[i]=ret[v];
} FWTand(ans,1<<n-1,-1);
for(int i=0;i<(1<<n-1);i++) printf("%lld ",ans[i]);
return 0;
}

Codeforces 1326F2 - Wise Men (Hard Version)(FWT+整数划分)的更多相关文章

  1. CodeForces 768E SG函数 整数划分 Game of Stones

    一个标准的NIM游戏 加上一条规则:每堆石子对于每个数目的石子只能被取一次 可以SG打表 dp[i][j]表示现在有i个石子 j是可以取的石子数的状压 第i位为1就表示i个石子没被取过 #includ ...

  2. 51nod p1201 整数划分

    1201 整数划分 基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 将N分为若干个不同整数的和,有多少种不同的划分方式,例如:n = 6,{6} {1,5} {2, ...

  3. 2014北大研究生推免机试(校内)-复杂的整数划分(DP进阶)

    这是一道典型的整数划分题目,适合正在研究动态规划的同学练练手,但是和上一个随笔一样,我是在Coursera中评测通过的,没有找到适合的OJ有这一道题(找到的ACMer拜托告诉一声~),这道题考察得较全 ...

  4. 整数划分 (区间DP)

    整数划分(四) 时间限制:1000 ms  |  内存限制:65535 KB 难度:3   描述 暑假来了,hrdv 又要留学校在参加ACM集训了,集训的生活非常Happy(ps:你懂得),可是他最近 ...

  5. nyoj 90 整数划分

    点击打开链接 整数划分 时间限制:3000 ms  |  内存限制:65535 KB 难度:3 描述 将正整数n表示成一系列正整数之和:n=n1+n2+-+nk,  其中n1≥n2≥-≥nk≥1,k≥ ...

  6. 整数划分 Integer Partition(二)

    本文是整数划分的第二节,主要介绍整数划分的一些性质. 一 先来弥补一下上一篇文章的遗留问题:要求我们所取的 (n=m1+m2+...+mi )中  m1 m2 ... mi连续,比如5=1+4就不符合 ...

  7. 整数划分 Integer Partition(一)

    话说今天百度面试,可能是由于我表现的不太好,面试官显得有点不耐烦,说话的语气也很具有嘲讽的意思,搞得我有点不爽.Whatever,面试中有问到整数划分问题,回答这个问题过程中被面试官搞的不胜其烦,最后 ...

  8. 51nod1201 整数划分

    01背包显然超时.然后就是一道神dp了.dp[i][j]表示j个数组成i的方案数.O(nsqrt(n)) #include<cstdio> #include<cstring> ...

  9. NYOJ-571 整数划分(三)

    此题是个非常经典的题目,这个题目包含了整数划分(一)和整数划分(二)的所有情形,而且还增加了其它的情形,主要是用递归或者说是递推式来解,只要找到了递推式剩下的任务就是找边界条件了,我觉得边界也是非常重 ...

随机推荐

  1. PG集群(PostgreSql环境)搭建

    centos PG集群搭建 一.安装PG 1.安装之前首先查看软件是否已经安装 rpm -qa | grep postgresql #若存在,需要卸载使用 yum remove postgresql ...

  2. [对对子队]会议记录4.21(Scrum Meeting12)

    今天已完成的工作 吴昭邦 ​ 工作内容:基本实现改变顺序合成 ​ 相关issue:实现流水线合成系统的逻辑 ​ 相关签入:4.21签入1 梁河览 ​ 工作内容:修改设置界面bug ​ 相关签入:4.2 ...

  3. PCB设计中新手和老手都适用的七个基本技巧和策略

    本文将讨论新手和老手都适用的七个基本(而且重要的)技巧和策略.只要在设计过程中对这些技巧多加注意,就能减少设计回炉次数.设计时间和总体诊断难点. 技巧一:注重研究制造方法和代工厂化学处理过程 在这个无 ...

  4. picGo+gitee搭建Obsidian图床,实现高效写作

    1 picGo安装 [picgo下载链接](https://molunerfinn.com/PicGo/) 选择安装目录,可以选择安装在D:\Program Files 然后点击安装即可 2. git ...

  5. 『学了就忘』Linux基础 — 9、虚拟机中快照的使用

    目录 1.快照的含义 2.快照的使用 步骤一:创建拍摄快照 步骤二:填写快照信息并创建 步骤三:查看快照 步骤四:操作快照 3.管理虚拟机小技巧 4.关于快照说明 快照和克隆是VMware中两个非常实 ...

  6. NOIP模拟86(多校19)

    T1 特殊字符串 解题思路 \(f_{i,j}\) 表示前 \(i\) 个字符中结尾为 \(j\) 的最大贡献. 转移枚举当前位置于之前位置结尾的组合加上贡献即可. 对于边界问题,容易发现选择 1 一 ...

  7. linux切换shell

    1. $SHELL这一环境变量用于保存当前用户使用的shell,所以我们可以输出$SHELL来查看当前使用的shell是什么: 2. 查看/etc/shells文件,可以看到当前系统中安装的有效的sh ...

  8. 我为啥开始用CSDN博客

    今晚开通CSDN博客,并且决定以后每天都使用这个不错的东西.与此同时,在博客园也开通了一个:http://www.cnblogs.com/fish7/ 我原本是把做过的题都用WPS整理的,然后每次打印 ...

  9. Vue面试题2

    Class与Style绑定工作有用过吗: 有,后台管理系统菜单.主题色切换 .tab选项卡等..... 计算属性和侦听器区别.使用场景: 计算属性有缓存.并且是响应式依赖缓存,调用不加小括号 利用vu ...

  10. sqlldr 导入有中文乱码问题

    1.导入成功后,查看导入数据有乱码 2.查看字符集为uft8 select * from v$nls_parameters where PARAMETER like '%NLS_CHARACTERSE ...