2025杭电多校第六场钥匙迷宫取模传送排序cats的max如何实现?

摘要:cats 的 max dp #子集合dp #状态压缩dp #状态压缩 题目 思路 本题只需要考虑(k<m)的情况,因为(kgeq m)时,每一列都必定可以选到其最大值,暴力即可算出答案 考虑到(mleq1
cats 的 max dp #子集合dp #状态压缩dp #状态压缩 题目 思路 本题只需要考虑\(k<m\)的情况,因为\(k\geq m\)时,每一列都必定可以选到其最大值,暴力即可算出答案 考虑到\(m\leq13\),所以对每行进行状态压缩 \(mp[N][N]\)用来储存矩阵 \(a[i][mask]\)代表第\(i\)行中,\(mask(2)\)所对应列的元素之和 如\(mask:10010\),则\(a[i][mask]=mp[i][1]+mp[i][4]\) \(mask\)中位置\(pos\)为1就代表第\(pos\)列的最大值取在\(mp[ i][pos]\) 如何预处理\(a[i][mask]\)呢? 如果对于每一行\(i\)所储存的\(2^m\)个\(mask\)进行遍历,把1的位置加进\(a[i][mask]\)中,时间复杂度将来到\(o(n·m·2^m)\),\(TLE\) 因此需要采用前缀优化:通过\(lowbit\)将\(mask\)拆分为\(lowbit\ ,mask\wedge lowbit\),通过递推\(a[i][mask]=a[i][lowbit]+a[i][mask \wedge lowbit]\)线性预处理出所有情况 \(lowbit\leq mask\ ,mask\wedge lowbit<mask\),而遍历\(mask\)时是从小到大遍历的,因此该递推即利用了之前处理好的前缀来优化复杂度 \(b[mask]\)代表所有\(a[i][mask]\ i\in[1,n]\)中的最大值 \(dp[cnt][mask]\)代表:现在选择了\(cnt\)行,这\(cnt\)行的状态或运算后为\(mask\)的权值最大值 状态转移: 遍历\(k\)次选择,对于一个\(mask\),其子集记为\(mask2\),则二者的补集为\(mask\wedge mask 2\) \[dp[cnt][mask2]\times b[mask\wedge mask 2]=dp[cnt+1][mask] \] 当前选了\(cnt\)行,状态为\(mask 2\),那么其与\(mask\wedge mask 2\)或运算后即可转移到\(mask\)状态,\(b[mask\wedge mask 2]\)就相当于选了新的一行,并且保证新的一行上的列最值与之前状态的列最值不会重叠(补集的作用) 一般dp由后往前写,所以修改一下即可: \[dp[cnt][mask]=dp[cnt-1][mask2]\times b[mask\wedge mask 2] \] 枚举所有的\(mask\)及其子集的复杂度为\(3^m\),所以总复杂度为\(o(m·3^m)\) 最后直接输出\(dp[k][one]\)即可 #include<iostream> #include<cstdio> #include<vector> #include<algorithm> #include<queue> #include<cmath> #include<unordered_map> #include<set> using namespace std; using ll = long long; #define rep(i, a, b) for(ll i = (a); i <= (b); i ++) #define per(i, a, b) for(ll i = (a); i >= (b); i --) #define see(stl) for(auto&ele:stl)cout<<ele<<" "; cout<<'\n'; const ll inf = 1e9 + 5; #define int ll const int N=1005,M=(1<<14); int mp[N][N],a[N][M],dp[N][M],b[M]; void see2(int x){ while(x){ cout<<(x&1); x>>=1; } } void eachT() { int n,m,k;cin>>n>>m>>k; int one=(1<<m)-1; rep(tim,1,k){ rep(mk,0,one)b[mk]=0,dp[tim][mk]=0; }
阅读全文