Edu 169 补题记录

F. Make a Palindrome

给定一个由 \(n\) 个整数组成的数组 \(a\)

让函数 \(f(b)\) 返回使数组 \(b\) 成为回文所需的最小操作次数。您可以进行的操作有:

  1. 选择两个相邻的元素 \(b_i\)\(b_{i+1}\) ,删除它们,并用一个元素 \((b_i + b_{i + 1})\) 替换它们;
  2. 或者选择一个元素 \(b_i> 1\) ,将其移除,并将其替换为两个正整数 \(x\)\(y\),满足 \(x + y = b_i\)

求出数组 \(a\) 的所有子数组的函数 \(f\) 的值之和。

问题分析:

不妨先分析两种操作对数列产生的影响。

操作 \(1\),合并两个元素,即减少一个元素,如果合并结果与另一侧的数字相同,都可以消去。

操作 \(2\),分裂一个元素,会增加一个元素,如果分裂数字与另一半相同,也可以消去。

但此时我们可以发现,操作 \(1\) 稳赚不赔,如果操作 \(2\) 分裂后能够连续执行两次消去(才可能比操作 \(1\) 优),那么操作 \(1\) 也可以通过合并构造。

所以可以抛弃操作 \(2\),只选操作 \(1\) 执行即可。

既然只有合并,考虑区间 \(dp\),设 \(f_{i,j}\) 表示 \(i\sim j\) 操作的最少次数,那么枚举要合并的两个端点 \(i\le l,r\le j\),即我们希望将 \(i\sim l-1\)\(r+1\sim j\) 范围内的数字分别合并然后相等消去,即满足如下条件(设 \(s_i\) 为前缀和):

\[s_{l-1}-s_{i-1}=s_{j}-s_{r}\\ \downarrow\\ s_{i-1}+s_j=s_{l-1}+s_r \]

那么有如下转移:

\[f_{i,j}=\min\{f_{l,r}+(l-1-i)+(j-r-1)\}\\ \downarrow\\ f_{i,j}=\min\{f_{l,r}+(l-r)+j-i-2\} \]

直接枚举时复 \(\rm O(n^4)\),无法通过,将满足条件按照上面式子转化,发现当我们枚举 \(i,j\) 时,等式左边已知,于是可以将等于所有 \(s_{l-1}+s_r\) 对应的 \(f_{l,r}+(l-r)\) 丢进一个 \(map\),维护满足条件的最小值。

但是此时发现一个问题:如何满足 \(l,r\)\(i,j\) 的范围关系,可以考虑倒序枚举 \(i\) ,这样 \(l\) 的限制满足,由于前缀和数组单调递增,所以 \(s_{l-1}\ge s_{i-1}\),那么若想相等,必须满足 \(s_{j}\le s_{r}\),于是满足 \(j\le r\),符合条件。

代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
LL read() {
    char c=getchar(); LL sum=0,flag=1;
    while(c<'0'||c>'9') {if(c=='-') flag=-1; c=getchar();}
    while(c>='0'&&c<='9') {sum=sum*10+c-'0'; c=getchar();}
    return sum*flag;
}

const int N=2100,INF=1e9;
int n,a[N],s[N];
int f[N][N];

void Solve() {
    n=read();
    for(int i=1;i<=n;i++) {
        a[i]=read();
        s[i]=s[i-1]+a[i];
    }
    map<int,int> cnt; cnt.clear();
    for(int i=n;i>=1;i--) {
        for(int j=i-1;j<=n;j++) {
            int t=s[j]+s[i-1];
            f[i][j]=j-i;
            if(cnt.count(t)) f[i][j]=min(f[i][j],cnt[t]+j-i);
            if(j<i) f[i][j]=0;

            int k=s[i-1]+s[j];
            if(!cnt.count(k)) cnt[k]=f[i][j]+i-j-2;
            else cnt[k]=min(cnt[k],f[i][j]+i-j-2);
        }
    }
    LL sum=0;
    for(int i=1;i<=n;i++) {
        for(int j=i;j<=n;j++) {
            sum+=f[i][j];
        }
    }
    cout<<sum<<endl;
}

void Clear() {
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=n;j++) {
            f[i][j]=0;
        }
    }
}

int main(){
    int T=read();
    while(T--){
        Solve();
        Clear();
    }

    return 0;
}
/*
*/

G. Substring Compression

让我们定义压缩字符串 \(t\) 的操作,该字符串至少包含从 \(1\)\(9\)\(2\) 个数字,如下所示:

将其拆分为偶数个非空子字符串,让这些子字符串为 \(t_1, t_2, \dots, t_m\) ( \(t = t_1 + t_2 + \dots + t_m\) ,其中 \(+\) 是连接操作);
先写入字符串 \(t_2\) \(t_1\) 次,再写入字符串 \(t_4\) \(t_3\) 次,以此类推。

例如,对于字符串“12345”,可以这样做:将其分成(“1”,“23”,“4”,“5”),并写入“235555”。

对于字符串 \(t\) ,让函数 \(f(t)\) 返回该过程可以获得的字符串的最小长度。

您将得到一个字符串 \(s\) ,由从 \(1\)\(9\) 的数字 \(n\) 和一个整数 \(k\) 组成。计算长度为 \(k\) 的所有连续的 \(s\) 子字符串的函数 \(f\) 的值。

问题分析:

矩阵妙妙题。

不妨先来思考,如果一个字符串只被分成两段,怎么分才能长度最短?

结论:直接在第一个字符后划一刀分成两段。

比如 \(23333\),当然是分成 \(2+3333\),这样长度为 \(2\times 4=8\),而不是 \(23+333\),这样长度为 \(23\times 3=69\)

于是可以设计 \(dp\),设 \(f_{i}\) 表示考虑 \(i\sim n\) 的最优解,那么有:

\[f_{i}=f_{j+1}+a_i\times (j-i)\\ \downarrow \\ f_{i}=(f_{j+1}+a_i\times j)-a_i\times i \]

观察到 \(a_i\in [1,9]\),设 \(g_{i}=f_{j+1}+i\times j\),那么方程再次改写为:

\[f_{i}=g_{a_i}-a_i\times i \]

考虑用矩阵维护。注:此处矩阵乘法的符号并不是 \(\times +\),而是 \(\min,+\),依旧符合矩阵乘法性质。

由于 \(g,f\) 最终的式子中含有 \(i\times j,g_{a_i},-a_i\times i,f_i\) 几种因素,所以考虑将他们都塞进矩阵。(由于 \(1\sim 9\) 数字太多不便展示,下文将用 \(1\sim 2\) 的例子示范,即 \(3\times 3\) 的矩阵)。

\[\begin{bmatrix} f_{id+1} & g_1 & g_2\\ \end{bmatrix} \]

现在通过计算,考虑了 \(id+1\sim n\) 的情况,思考该矩阵应乘上什么得到新矩阵。

事实上,可以这样(矩阵行列从 \(0\) 编号):

\[\begin{bmatrix} f_{id+1} & g_1 & g_2 \end{bmatrix} \times \begin{bmatrix} \infty & id\times 1 & id\times 2\\ -a_{id}\times id & 0 & \infty\\ \infty & \infty & 0 \end{bmatrix} \]

注:\(-a_{id}\times id\) 并不是放在第 \(1\) 行,而是放在第 \(id\) 行的第 \(0\) 个位置,所以,图中的 \(id=1\),即为下图:

\[\begin{bmatrix} f_{id+1} & g_1 & g_2 \end{bmatrix} \times \begin{bmatrix} \infty & id\times 1 & id\times 2\\ -a_{1}\times 1 & 0 & \infty\\ \infty & \infty & 0 \end{bmatrix} \]

我们来模拟一下:

第一行乘第一列,得到:\(f_{id}=\min\{f_{id+1}+\infty,g_1-a_{1}\times 1,g_2\times \infty\}=g_1-a_{1}\times 1\)

第一行乘第二列,得到:\(g_1=\min\{f_{id+1}+id\times 1,g_1+0,g_2+\infty\}=\min\{g_1,f_{id+1}+id\times 1\}\)

第一行乘第三列,同上。

所以我们设置第 \(i\) 个字母的矩阵为:

\[\begin{bmatrix} \infty & id\times 1 & id\times 2\\ -a_{id}\times id & 0 & \infty\\ \infty & \infty & 0 \end{bmatrix} \]

注意其中 \(-a_{id}\times id\) 的位置。

注意矩阵乘法的顺序,不满足交换律,一定是从后向前乘。

题目还要求所有连续 \(k\) 个的答案,可以将序列分成若干长度为 \(k\) 的块,对于每个块,维护前缀积和后缀积,那么一段长度为 \(k\) 的区间就可以用前缀和后缀拼出来,注意乘的顺序。

时间复杂度:\(\rm O(100n)\)

代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
LL read() {
    char c=getchar(); LL sum=0,flag=1;
    while(c<'0'||c>'9') {if(c=='-') flag=-1; c=getchar();}
    while(c>='0'&&c<='9') {sum=sum*10+c-'0'; c=getchar();}
    return sum*flag;
}

const int N=2e5+10;
int n,k;
string s;

struct Matrix {
    int ma[10][10];
    Matrix() {memset(ma,0x3f,sizeof(ma));}
    friend Matrix operator * (Matrix a,Matrix b) {
        Matrix c;
        for(int i=0;i<10;i++) {
            for(int j=0;j<10;j++) {
                for(int k=0;k<10;k++) {
                    c.ma[i][j]=min(c.ma[i][j],a.ma[i][k]+b.ma[k][j]);
                }
            }
        }
        return c;
    }
}a[N],pre[N],suf[N];

Matrix init(int id,int k) {
    Matrix c;
    for(int i=1;i<=9;i++) {
        c.ma[0][i]=id*i;
        c.ma[i][i]=0;
    }
    c.ma[k][0]=-id*k;
    return c;
}

void Solve() {
    cin>>n>>k>>s; s=" "+s;
    for(int i=1;i<=n;i++) a[i]=init(i,s[i]-'0');
    for(int i=1;i<=n;i++) {
        if(i%k==1) {
            pre[i]=a[i];
        }
        else {
            pre[i]=a[i]*pre[i-1];
        }
    }
    suf[n]=a[n];
    for(int i=n-1;i>=1;i--) {
        if(i%k==0) {
            suf[i]=a[i];
        }
        else {
            suf[i]=suf[i+1]*a[i];
        }
    }
    for(int i=1;i+k-1<=n;i++) {
        if(i%k==1) cout<<pre[i+k-1].ma[0][0]<<" ";
        else cout<<(pre[i+k-1]*suf[i]).ma[0][0]<<" ";
    }
}

void Clear() {

}

int main(){
    int T=1;
    while(T--){
        Solve();
        Clear();
    }

    return 0;
}
/*
*/
posted @ 2024-08-18 23:10  2017BeiJiang  阅读(40)  评论(1编辑  收藏  举报