首页 > 其他分享 >Edu 169 补题记录

Edu 169 补题记录

时间:2024-08-18 23:17:27浏览次数:9  
标签:infty 矩阵 times 补题 bmatrix Edu 字符串 id 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;
}
/*
*/

标签:infty,矩阵,times,补题,bmatrix,Edu,字符串,id,169
From: https://www.cnblogs.com/zhangyuzhe/p/18366330

相关文章

  • Educational Codeforces Round 168 (Rated for Div. 2) D题
    文章目录题目来源题意思路code题目来源D.MaximizetheRoot题意给定一棵n个点的数,根节点为1,每个点都有权值aia_i......
  • AtCoder Beginner Contest 367 补题记录(A~F)
    很Easy一场,共计用时\(34\)minAconstintN=1000100;signedmain(){strings;cin>>s;intcnt=0;intn=s.size();if(s[n-1]=='0'&&s[n-2]=='0'&&s[n-3]=='0'){s.pop_back();s.p......
  • Scheduler工作流程
    Scheduler的目的React实现Scheuler的目的是想要实现时间切片。时间切片是指:将长任务拆分成多段,每次执行一小段的任务的操作。Scheduler的实现React利用MessageChannel创建出一个port实例,port实例有两个属性port1和port2。如果在Scheduler当中调用port2.postM......
  • DolphinScheduler集群部署问题(趟坑)总结
    目录官方文档官方项目地址问题解决官方文档DolphinScheduler|文档中心(apache.org)官方项目地址部署及使用过程中的问题可以参见项目Issue:Issues·apache/dolphinscheduler·GitHubGitHub-apache/dolphinschedulerat3.2.2-release问题解决1、JVM在运......
  • Splitting Items(Round 169)
    #include<bits/stdc++.h>#defineendl'\n'#defineintllusingll=longlong;typedefunsignedlonglongull;usingnamespacestd;voidGordenGhost();signedmain(){#ifdefGordenfreopen("in.txt","rt",stdin......
  • Codeforces 169 Div2
    AClosestPoint由题意可得三个及以上的点无法插入新的点,只有两个点时可以插入但当两个点间隔为1时同样无法插入先判断,后输出就行#include<bits/stdc++.h>usingnamespacestd;constintN=50;intt,n;inta[N];intmain(){ cin>>t; while(t--){ cin>>n; for(i......
  • Redux 中间件的实现原理
    Redux中间件的实现原理主要基于函数式编程思想和高阶函数。中间件用于在Redux的dispatch过程之间插入自定义逻辑,如日志记录、异步操作、调试工具等。1.什么是Redux中间件?简要介绍Redux中间件的概念和用途。解释中间件如何在dispatch动作和到达reducer之间插入逻......
  • TaskScheduler
    TaskScheduler是什么TaskScheduler决定了将Task调度到什么地方去执行,即TaskScheduler决定了Task如何被调度ThreadPoolTaskScheduler如果不特别指定,默认就是ThreadPoolTaskScheduler内部有两种处理逻辑,一种是针对LongRunning需求的Task,会单独走后台Thread路径;另一种是非LongRu......
  • codeforces ECR169
    codeforcesECR169A#include<bits/stdc++.h>usingnamespacestd;constintmaxn=50;inta[maxn];voidsolve(){intn;cin>>n;for(inti=1;i<=n;i++)cin>>a[i];if(n==2){if((a[2]-a[1])!=1){......
  • .NET 轻量化定时任务调度 FreeScheduler
    前言在平时项目开发中,定时任务调度是一项重要的功能,广泛应用于后台作业、计划任务和自动化脚本等模块。FreeScheduler是一款轻量级且功能强大的定时任务调度库,它支持临时的延时任务和重复循环任务(可持久化),能够按秒、每天/每周/每月固定时间或自定义间隔执行(CRON表达式)。此外......