[状压dp]CF1209E2 题解

相信题意大家已经都了解了,我们思考一下如何解决这道题目。

首先数据范围是 \(n\leq12\),在这个范围内能做的估计是指数级的复杂度。

因为 \(m\) 很大,我们要尝试能不能把他弄掉。

我们只可能用到列里最大值最大的前 \(n\) 个列

证明:

假设我们用到了最大值最第 \(n+1\) 大的那个列。假如说他用到了这个列里的某些值,那么这些值肯定没有那些 \(i,i\leq n\) 列的最大值大,那么我们大可以用那些前 \(n\) 列的最大值替换现在这个列里用到的那些值,这样只会使答案更优。

我们已经将 \(m\) 也降到了 \(n\) 的级别了。

思考一下如何暴力。这就是 E1。直接令 \(dp_{i,j}\) 表示现在选到了第 \(i\) 行,已经确定选那些行的状态是 \(j\) 的最大值。转移就枚举 \(j\) 的子集 \(k\) 表示上一行中已经确定的状态。那么就有状态转移方程 \(dp_{i,j}=dp_{i,k}+w_{k\ xor \ j}\) 其中 \(w_k\) 表示现在这一行中我已经确定的状态经过移位后的最大值。

有聪明的彦祖就会问了,如果我这一位最后明明不是行里的最大值,但是还是把他当做了最大值,这不是错误的么?

当然不是,因为就算这种选法是错误的,正是因为他不是行里的最大值,所以取最大的时候他肯定没正确的选法更大,所以不会对答案造成影响。


然后我们计算一下时间复杂度。

枚举子集的复杂度是 \(\mathcal{O}(3^n)\) 这里也稍微证明一下

令二元组 \((S,T),T\subseteq S\) 这就表示我们枚举的集合 \(S\) 和子集 \(T\) 。我们思考一下每一个元素可能的情况

  1. \(k\in S,k\notin T\)
  2. \(k\in S,k\in T\)
  3. \(k\notin S,k\notin T\)

所以对于每一种元素有 \(3\) 种情况,那么总时间复杂度就是 \(\mathcal{O}(3^n)\)

话说上文,枚举了子集之后,我们还要计算 \(w_{k\ xor\ j}\) 的值,因为还要移位所以计算 \(w_{k\ xor\ j}\) 的复杂度是 \(\mathcal{O}(n^2)\) 总时间复杂度就是 \(\mathcal{O}(n^33^n)\) 这肯定是过不了的。

我们注意到 \(w_{l}\) 的值是不会变的,我们枚举所有集合预处理出 \(w\) 数组。

总时间复杂度即为 \(\mathcal{O}(n(2^nn^2+3^n))=\mathcal{O}(n^32^n+n3^n)\)

主要难度都在代码上了。

#include <bits/stdc++.h>
#define debug puts("I love Newhanser forever!!!!!");
#define pb push_back
using namespace std;
template <typename T>inline void read(T& t){
    t=0; register char ch=getchar(); register int fflag=1;
    while(!('0'<=ch&&ch<='9')){if(ch=='-') fflag=-1;ch=getchar();}
    while(('0'<=ch&&ch<='9')){t=t*10+ch-'0'; ch=getchar();} t*=fflag;
}
template <typename T,typename... Args> inline void read(T& t, Args&... args){read(t);read(args...);}
const int MAXN=13;
int T,n,m;
struct Col{
    int a[MAXN],maxx;
    void Clear(){maxx=0;}
    void Intt(){for(int i=0;i<n;++i) maxx=max(maxx,a[i]);}
    bool operator < (const Col &col) const{
        return maxx>col.maxx;
    }
}c[5145];
int w[1<<MAXN],dp[MAXN][1<<MAXN];
int main(){
    read(T);
    while(T--){
        read(n,m);
        memset(dp,0,sizeof(dp));
        for(int i=0;i<m;++i) c[i].Clear();
        for(int i=0;i<n;++i){
            for(int j=0;j<m;++j)
                read(c[j].a[i]);
        }
        for(int j=0;j<m;++j) c[j].Intt();
        sort(c,c+m);
        /*for(int i=0;i<min(n,m);++i)
            for(int j=0;j<n;++j)
                cout<<c[i].a[j]<<endl;*/
        for(int i=0;i<min(n,m);++i){
            //现在是第 i 列
            for(int j=0;j<(1<<n);++j){
                //现在选的状态是 j
                w[j]=0;
                for(int k=0;k<n;++k){
                    //进行了 k 次循环移位.
                    int res=0;
                    for(int l=0;l<n;++l)
                    if((1<<l)&j) res+=c[i].a[(l+k)%n];
                    w[j]=max(w[j],res);
                }
            }
            for(int j=0;j<(1<<n);++j){
                //现在选择的状态是 j.
                if(i==0){
                    dp[i][j]=w[j];
                    continue;
                }
                dp[i][j]=dp[i-1][j];
                for(int k=j;k;k=(k-1)&j) dp[i][j]=max(dp[i][j],dp[i-1][k]+w[k^j]);
            }
        }
        cout<<dp[min(n,m)-1][(1<<n)-1]<<endl;
    }
    return 0;
}
//Welcome back,Newhanser.
posted @ 2022-07-15 17:23  Mercury_City  阅读(93)  评论(0编辑  收藏  举报