dp交换值域定义域

前言

最近心血来潮学了一下这个套路?感觉很高级 qwq。

正文

我不好归纳,但他们大体都是一样的。

T1

[AGC033D] Complexity

题意

给定一个 \(N\)\(M\) 列的字符矩阵。
我们定义一个字符矩阵的凌乱度为:
若这个字符矩阵中所有字符都相同,则凌乱度为 \(0\)
否则,则考虑所有的沿水平或者竖直方向的直线,将字符矩阵分成两个不为空的部分,设两个部分的凌乱度分别为 \(a\)\(b\),则整个字符矩阵的凌乱度为 \(\max(a,b)+1\) 的最小值。
请你求出,给出的字符矩阵的凌乱度是多少。
\(1 \leq N, M \leq 185\)

Solution

我学习的是 Alex_wei 的博客。在这里自己阐述一遍加深理解qaq。

先考虑答案上限,就是全不相同,每次从中间切开,所以上界是 \(O(\log n + \log m)\) 的。

首先考虑朴素 \(dp\),形如 \(f[i][j][k][l]\) 为求出左上角 \((i,j)\) 右下角 \((k,l)\) 的答案。我们发现状态数就是 \(O(n^4)\) 的,转移复杂度很爆炸。

再进行一步观察发现一个显然的性质:

  • 矩形之间存在包含关系的时候,答案一定不降。

再深入一点:

  • 对于固定了左、上、下边界 \((i,j,k)\) 的矩形,随着右边界的增加答案不降。

于是考虑令 \(dp[i][j][k][x]\) 表示固定了左、上、下边界为 \((i,j,k)\) 的矩形,此时答案不超过 \(x\) 能到达的最右边界。

这实际上就是交换值域定义域了,非常巧妙,并且降低了复杂度,因为我们的答案是 \(O(\log)\) 级别的。

然后就是这个 \(x\) 因为每次最多加一,可以滚动数组,他没啥意义。

答案就是 \(dp[1][1][n] == m\) 的时候的 \(x\)

下面的 \(f,g\) 是滚动数组。

转移分两种:

  • 横着切类似区间 \(dp\) 的转移

    \[f[i][j][k] = max(min(g[i][j][p],g[i][p + 1][k])) \]

  • 竖着切就类似倍增的转移

    \[f[i][j][k] = max(g[g[i][j][k] + 1][j][k]) \]

于是做完了 qaq 。

点击查看代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i<(b);++i)
#define rrep(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
template <typename T>
inline void read(T &x){
    x=0;char ch=getchar();bool f=0;
    while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    if(f)x=-x;
}
template <typename T,typename ...Args>
inline void read(T &tmp,Args &...tmps){read(tmp);read(tmps...);}
const int N = 190;
int n,m;
char s[N][N];
int f[N][N][N],ff[N][N][N];
int s1[N][N],s2[N][N];
inline int q1(int x1,int y1,int x2,int y2){
    return s1[x2][y2] + s1[x1 - 1][y1 - 1] - s1[x2][y1 - 1] - s1[x1 - 1][y2];
}
inline int q2(int x1,int y1,int x2,int y2){
    return s2[x2][y2] + s2[x1 - 1][y1 - 1] - s2[x2][y1 - 1] - s2[x1 - 1][y2];
}
signed main(){
    read(n,m);
    rep(i,1,n)scanf("%s",s[i] + 1);
    rep(i,1,n)rep(j,1,m){
        s1[i][j] = s1[i - 1][j] + s1[i][j - 1] - s1[i - 1][j - 1] + (s[i][j] == '#');
        s2[i][j] = s2[i - 1][j] + s2[i][j - 1] - s2[i - 1][j - 1] + (s[i][j] == '.');
    }
    rep(k,1,m){
        rep(l,1,n){
            int now = m;
            rep(r,l,n){
                while(q1(l,k,r,now) && q2(l,k,r,now))--now;
                f[k][l][r] = now;
            }
        }
    }
    for(int i = 0;; ++ i){
        if(f[1][1][n] == m){
            printf("%d\n",i);
            return 0;
        }
        rep(k,1,m){
            rep(l,1,n){
                int p = l;
                rep(r,l,n){
                    #define val(p) min(f[k][l][p],f[k][p + 1][r])
                    while(p + 1 < r && val(p) <= val(p + 1))++p;
                    ff[k][l][r] = max({val(p),f[k][l][r],f[f[k][l][r] + 1][l][r]});
                }
            }
        }
        swap(f,ff);
    }
}

T2

CF1342F

题意

长度为 \(n\) 的序列,\(n \le 15\) ,每次可以进行操作形如:选择 \(i,j\),令 \(a[j] += a[i]\) ,删除 \(a[i]\),求令序列严格递增的最小次数并输出方案。

Solution

神仙题!!!

题意等价于将序列划分成若干个集合,使得最后可以排成一个严格单增的序列。

那么考虑 \(f[i][j][k]\) 为考虑完前 \(i\) 个集合,第 \(i\) 个集合全部加在了 \(j\) 的身上,已经用过的集合为 \(k\) 时,第 \(i\) 个集合的最小值。

转移的时候直接枚举没选过的集合是什么,扔进第 \(i + 1\) 个集合里面,并且要保证扔在 \(p\) 右边的最左边的位置。

同理答案就是,从大往小枚举 \(i\) ,如果存在答案,就是他了!直接输出路径即可qaq。

点击查看代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i<(b);++i)
#define rrep(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
template <typename T>
inline void read(T &x){
    x=0;char ch=getchar();bool f=0;
    while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    if(f)x=-x;
}
template <typename T,typename ...Args>
inline void read(T &tmp,Args &...tmps){read(tmp);read(tmps...);}
const int N = 20;
int n,a[N];
const int M = (1 << 15) + 5;
int s[M],dp[N][N][M],id[N];
using pii = pair<int,int>;
struct qwq{
    int i,p,s;
};
#define endl putchar('\n')
#define spac putchar(' ')
template <typename T>
void write(T x){
    if(x >= 10)write(x / 10);
    putchar(x % 10 + '0');
}
qwq fa[N][N][M];
//dp[i][j][k]
//有i个集合,第i个都合并到j,已经选的集合为k,第i个集合最小值
const int INF = 0x3f3f3f3f;
#define pop(x) __builtin_ctz(x)
inline void ckmin(int &x,int y){if(x > y)x = y;}
inline void upd(qwq u,qwq v,int w){
    ckmin(dp[v.i][v.p][v.s],w);
    if(dp[v.i][v.p][v.s] == w)fa[v.i][v.p][v.s] = u;
}
inline void solve(){
    read(n);
    Rep(i,0,n)read(a[i]);
    Rep(st,0,1 << n){
        s[st] = 0;
        Rep(j,0,n)if(st >> j & 1)s[st] += a[j];
    }
    rep(i,0,n)rep(j,0,n)Rep(k,0,1 << n)dp[i][j][k] = INF;
    dp[0][0][0] = 0;
    Rep(i,0,n)Rep(j,0,n)Rep(k,0,1 << n)if(dp[i][j][k] != INF){
        qwq u = {i,j,k};
        int st = (((1 << n) - 1) ^ k);
        for(int s1 = st;s1;s1 = (s1 - 1) & st){
            if(s[s1] > dp[i][j][k] && (s1 >> j)){
                qwq v = {i + 1,j + 1 + pop(s1 >> j),k | s1};
                upd(u,v,s[s1]);
            }
        }
    }
    qwq ans = (qwq){-1,-1,-1};
    int to = (1 << n) - 1;
    rrep(i,n,1){
        rep(j,1,n)
            if(dp[i][j][to] != INF){
                ans = {i,j,to};
                break;
            }
        if(~ans.i)break;
    }
    // printf("%d\n",n - ans.i);
    write(n - ans.i);
    endl;
    Rep(i,0,n)id[i] = i + 1;
    while(ans.i != 0){
        qwq pre = fa[ans.i][ans.p][ans.s];
        int s0 = pre.s ^ ans.s;
        Rep(i,0,n){
            if((s0 >> i & 1) && (i != ans.p - 1)){
                // printf("%d %d\n",id[i],id[ans.p - 1]);
                write(id[i]);spac;write(id[ans.p - 1]);endl;
                Rep(j,i + 1,n)id[j]--;
            }
        }
        ans = pre;
    }
}
signed main(){
    int t;
    read(t);
    while(t--)solve();
}
posted @ 2022-11-18 09:23  Xu_brezza  阅读(64)  评论(0编辑  收藏  举报