「刷题笔记」DP优化-状压-EX
棋盘
需要注意的几点:
- 题面编号都是从0开始的,所以第1行实际指的是中间那行
- 对\(2^{32}\)取模,其实就是\(unsigned\ int\),直接自然溢出啥事没有
- 棋子攻击范围不会旋转
首先,我们得找出所有满足条件的同一行的状态,在此之前,我们要先处理出状态\(S\)下,第\(k\)行能被棋子攻击到的格子\(at[k][S]\)
先钦定自己不能攻击自己
再枚举每一个状态中每一个1,然后对攻击范围模板进行左移/右移操作,把这一行这种状态能攻击到的格子或上他
接下来,枚举每一个状态,如果\(at[1][i]\ and\ i==0\),则记录下这种合法的情况。
这个时候我们就可以枚举每一行的状态来求解了,但是这样时间复杂度是\(O(n\cdot 2^{2m})\)的,而在\(n\)给到\(1e6\)的情况下无疑会炸掉,需要考虑进一步的优化
(颓了一下题解)
其实,每一种状态都是由之前的某种状态转移来的,且可以重复,那就说明如果两个状态可以排在一起,他们就可以多次排在一起
考虑矩阵快速幂优化
设初始矩阵中\(A[i][j]=1\)表示\(j\)可以排在\(i\)的后面
则有这样的柿子:\(f[i][j]=f[i][k]\cdot f[k][j]\)
这样的情况下,我们求\(A^n\),因为第一种情况是为空,所以\(\sum\limits_{i=1}^{cnt}A^n[1][i]\)即为答案,意为开始为空,经过\(n\)行后最后一行为状态\(i\)的总数
code:
#include<bits/stdc++.h>
using namespace std;
#define ll unsigned int
#define ull unsigned long long
#define ZZ_zuozhe
#define M 6
#define N 80
ll n,m,p,k;
ll lim[4];
ll at[4][1<<M];
ll sit[N],cnt=0;
struct mat
{
ll a[N][N];
mat(){memset(a,0,sizeof a);}
};
mat operator*(mat a,mat b)
{
mat c;
for(int i=1;i<=cnt;i++)for(int j=1;j<=cnt;j++)for(int k=1;k<=cnt;k++)
{
c.a[i][j]=c.a[i][j]+a.a[i][k]*b.a[k][j];
}
return c;
}
mat ksm(mat a,ll b)
{
mat res=a;
b--;
while(b)
{
if(b&1)res=res*a;
a=a*a;
b>>=1;
}
return res;
}
mat a,ans;
ll t;
int main()
{
scanf("%u%u%u%u",&n,&m,&p,&k);
for(int i=0;i<3;i++)
{
for(int j=0;j<p;j++)
{
scanf("%u",&t);
if(t)lim[i]|=(1<<j);
}
}
lim[1]-=(1<<k);
ll all=(1<<m)-1;
for(int i=0;i<=all;i++)
{
at[0][i]=at[1][i]=at[2][i]=0;
for(int j=0,p=i;p;j++,p>>=1)
{
if((p&1)==0)continue;
at[0][i]|=(j<k)?lim[0]>>(k-j):lim[0]<<(j-k);
at[1][i]|=(j<k)?lim[1]>>(k-j):lim[1]<<(j-k);
at[2][i]|=(j<k)?lim[2]>>(k-j):lim[2]<<(j-k);//处理每种状态每行能够攻击到的位置集合
}
}
for(int i=0;i<=all;i++)
{
if((i&at[1][i])==0)sit[++cnt]=i;
}
for(int i=1;i<=cnt;i++)
{
for(int j=1;j<=cnt;j++)
{
if(((sit[i]&at[0][sit[j]])==0)&&((sit[j]&at[2][sit[i]])==0))a.a[i][j]++;
//处理初始矩阵,如果j放在i后面不冲突就++
}
}
a.a[1][1]=1;
ans=ksm(a,n);
ll aa=0;
for(int i=1;i<=cnt;i++)
{
aa+=ans.a[1][i];
}
printf("%u\n",aa);
return 0;
}
最大前缀和
开始的思路
其实求最大前缀和似乎就是每个负数前判一次……
这几个数总排列方式是\(n!\),答案要乘他就说明直接拿可能值\(\cdot\)这种情况的次数求和就行……
\(n\)的范围看起来挺合适拿来状压的
口胡一下:\(f[i][S]\)为选\(i\)个当前状态为\(S\)的期望和
如果新取一个负数答案应该是直接加,取正数就得更新,但是好像也不一定会更新……
是不是得记录下当前值和更新答案的临界值什么的……
或者用状压记录负数位置……?
(实际证明没一点挨上边)
正解
妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊妙啊
状压还能这么出的吗,爱了爱了(虽然做不出来吧……
深挖一下最大前缀和的性质:
\]
设数列\(A\)的最大前缀和为\(pre_j\),即\(\sum\limits_{i=1}^{j} a_i\)
那么由此可以推出:
- 对于任意\(1\leq x \leq j\),有\(\sum\limits_{i=x}^{j} A_i\)定不小于0(否则可以把这部分减去)
- 对于任意\(j+1\leq x \leq n\),有\(\sum\limits_{i=j+1}^{x} A_i\)定不大于0(否则可以把这部分加上)
然后,我们发现,原数列可以分成两部分,分别满足不同的条件,既然如此,我们把满足左边条件的排列个数与满足右边条件的排列个数相乘,就可以得到总方案个数。
但是,题目中要求我们求最大前缀和的期望\(\cdot n!\),也就是用每种可能值乘上能得到它的方案个数,所以,结合状压,我们可以将每种状态满足左右的排列个数相乘,再把每种状态的满足条件方案个数乘上每种状态选的数的总和,即为所求
对于右边,我们在一个所有前缀和为负的数列后面加一个数,若新数列总和仍然为负,则新数列所有前缀和为负
对于左边,我们在一个所有后缀和为正的数列前面加一个数,若新数列总和仍然为非负,则新数列所有后缀和为非负
基于这样的过程,我们可以枚举子集,则有
\]
答案为
\]
另外注意:答案要求非负整数,所以最后要模成正的
code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define N 21
#define mod 998244353
#define S (1<<20)
ll n,all;
ll a[N];
ll sum[S];
ll f[S],g[S];
ll ans=0;
int main()
{
scanf("%lld",&n);
all=(1<<n)-1;
for(int i=0;i<n;i++)scanf("%lld",&sum[1<<i]);
for(int i=0;i<=all;i++)sum[i]=(sum[i&(-i)]+sum[i^(i&-i)])%mod;
g[0]=1;
for(int i=0;i<=all;i++)
{
if(sum[i]<0)
{
for(int j=0;j<n;j++)
{
if(i&(1<<j))
{
g[i]=(g[i]+g[i^(1<<j)])%mod;
}
}
}
}
f[0]=1;
for(int i=0;i<=all;i++)
{
if(sum[i]>=0)
{
for(int j=0;j<n;j++)
{
if((i&(1<<j))==0)
{
f[i|(1<<j)]=(f[i|(1<<j)]+f[i])%mod;
}
}
}
}
for(int i=0;i<=all;i++)
{
//cout<<sum[i]<<' '<<f[i]<<' '<<g[all^i]<<endl;
ans=(ans+sum[i]*f[i]%mod*g[all^i]%mod+mod)%mod;
}
printf("%lld",ans);
return 0;
}
「刷题笔记」DP优化-状压-EX的更多相关文章
- 【刷题笔记】DP优化-状压
因为篇幅太长翻着麻烦,计划把DP拆成几个小专题,这里原文只留下状压,其他请至后续博文. 状态压缩优化 所谓状态压缩,就是将原本需要很多很多维来描述,甚至暴力根本描述不清的状态压缩成一维来描述. 时间复 ...
- 「刷题笔记」AC自动机
自动AC机 Keywords Research 板子题,同luoguP3808,不过是多测. 然后多测不清空,\(MLE\)两行泪. 板子放一下 #include<bits/stdc++.h&g ...
- 「刷题笔记」哈希,kmp,trie
Bovine Genomics 暴力 str hash+dp 设\(dp[i][j]\)为前\(i\)组匹配到第\(j\)位的方案数,则转移方程 \[dp[i][j+l]+=dp[i-1][j] \] ...
- 【刷题笔记】DP优化-斜率优化
斜率优化,是一种利用斜率的优化(废话) 关于数论:咕咕咕 部分内容参考自学长 如果有这样的一个状态转移方程: \[f[i]=\min\limits_{j=L_j}^{R_j}\{f[j]+val(j, ...
- 【刷题笔记】DP优化-单调队列优化
单调队列优化 眼界极窄的ZZ之前甚至不会单调队列--(好丢人啊) 单调队列优化的常见情景: 转移可以转化成只需要确定一个维度,而且这个维度的取值范围在某个区间里 修剪草坪 这个题学长讲的好像是另外一个 ...
- 「刷题笔记」LCA问题相关
板子 ll lg[40]; ll dep[N],fa[N][40]; ll dis[N]; void dfs(ll u,ll f) { dep[u]=dep[f]+1; fa[u][0]=f; for ...
- 「刷题笔记」Tarjan
贴一个讲得非常详细的\(tarjan\)入门教程 信息传递 讲个笑话:我之前用并查集求最小环过的这题,然后看见题目上有个\(tarjan\)标签 留下了深刻的印象:\(tarjan\)就是并查集求最小 ...
- 《Data Structures and Algorithm Analysis in C》学习与刷题笔记
<Data Structures and Algorithm Analysis in C>学习与刷题笔记 为什么要学习DSAAC? 某个月黑风高的夜晚,下班的我走在黯淡无光.冷清无人的冲之 ...
- PTA刷题笔记
PTA刷题记录 仓库地址: https://github.com/Haorical/Code/tree/master/PTA/GPLT 两周之内刷完GPLT L2和L3的题,持续更新,包括AK代码,坑 ...
随机推荐
- MyBatis重要核心概念
一.SqlSessionFactoryBuilder 从命名上可以看出,这个是一个 Builder 模式的,用于创建 SqlSessionFactory 的类.SqlSessionFactoryBui ...
- Python list函数
- Java进阶专题(十六) 数据结构与算法的应用(上)
前言 学习算法,我们不需要死记硬背那些冗长复杂的背景知识.底层原理.指令语法--需要做的是领悟算法思想.理解算法对内存空间和性能的影响,以及开动脑筋去寻求解决问题的最佳方案.相比编程领域的其他技术 ...
- Positions of Large Groups
Positions of Large Groups In a string S of lowercase letters, these letters form consecutive groups ...
- 关于H5页面在微信浏览器中音视频播放的问题
Android 上,因为各个软件使用的浏览器渲染引擎不一样,所以视频播放的效果差异也很大,这里主要以微信为主.微信使用的是腾讯浏览器自带的X5内核. 而iOS是不允许使用第三方浏览器内核的,就是Goo ...
- 最长公共子串算法(Longest Common Substring)
给两个字符串,求两个字符串的最长子串 (例如:"abc""xyz"的最长子串为空字符串,"abcde"和"bcde"的最 ...
- Blog.Core 项目已完成升级.NET5.0
(是时候拿出来这种图了) 本文首发于公众号,但是会有新的内容加进来,所以就在博客园新开了一篇,望见谅.截止发稿,Blog.Core项目Master分支已经迁移到了5.0,新建了3.1的分支. 开心的锣 ...
- Linux下git push、git pull等指令需要输入账号密码 - 免除设置
打开终端按顺序执行下面的指令: 1.cd ~ 2.touch .git-credentials 3.vim .git-credentials 然后在打开的文件里面输入 https://{usernam ...
- 虚拟机vbox给vdi增加容量到16G后的一系列操作
虚拟机vbox给vdi增加容量到16G后的一系列操作windows 下:cmdVbox/bin下1.#VBoxManage modifyhd "cloned.vdi" --resi ...
- axios封装接口
我们一般都是在做一个大型项目的时候,需要用到很多接口时,我们为了方便使用,就把接口封装起来. 先安装axios命令 :npm install axios --save 那么思路是什么呢? 首先在src ...