Edu 169 补题记录
F. Make a Palindrome
给定一个由 个整数组成的数组 。
让函数 返回使数组 成为回文所需的最小操作次数。您可以进行的操作有:
- 选择两个相邻的元素 和 ,删除它们,并用一个元素 替换它们;
- 或者选择一个元素 ,将其移除,并将其替换为两个正整数 和 ,满足 。
求出数组 的所有子数组的函数 的值之和。
问题分析:
不妨先分析两种操作对数列产生的影响。
操作 ,合并两个元素,即减少一个元素,如果合并结果与另一侧的数字相同,都可以消去。
操作 ,分裂一个元素,会增加一个元素,如果分裂数字与另一半相同,也可以消去。
但此时我们可以发现,操作 稳赚不赔,如果操作 分裂后能够连续执行两次消去(才可能比操作 优),那么操作 也可以通过合并构造。
所以可以抛弃操作 ,只选操作 执行即可。
既然只有合并,考虑区间 ,设 表示 操作的最少次数,那么枚举要合并的两个端点 ,即我们希望将 和 范围内的数字分别合并然后相等消去,即满足如下条件(设 为前缀和):
那么有如下转移:
直接枚举时复 ,无法通过,将满足条件按照上面式子转化,发现当我们枚举 时,等式左边已知,于是可以将等于所有 对应的 丢进一个 ,维护满足条件的最小值。
但是此时发现一个问题:如何满足 与 的范围关系,可以考虑倒序枚举 ,这样 的限制满足,由于前缀和数组单调递增,所以 ,那么若想相等,必须满足 ,于是满足 ,符合条件。
代码:
#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
让我们定义压缩字符串 的操作,该字符串至少包含从 到 的 个数字,如下所示:
将其拆分为偶数个非空子字符串,让这些子字符串为 ( ,其中 是连接操作);
先写入字符串 次,再写入字符串 次,以此类推。例如,对于字符串“12345”,可以这样做:将其分成(“1”,“23”,“4”,“5”),并写入“235555”。
对于字符串 ,让函数 返回该过程可以获得的字符串的最小长度。
您将得到一个字符串 ,由从 到 的数字 和一个整数 组成。计算长度为 的所有连续的 子字符串的函数 的值。
问题分析:
矩阵妙妙题。
不妨先来思考,如果一个字符串只被分成两段,怎么分才能长度最短?
结论:直接在第一个字符后划一刀分成两段。
比如 ,当然是分成 ,这样长度为 ,而不是 ,这样长度为 。
于是可以设计 ,设 表示考虑 的最优解,那么有:
观察到 ,设 ,那么方程再次改写为:
考虑用矩阵维护。注:此处矩阵乘法的符号并不是 ,而是 ,依旧符合矩阵乘法性质。
由于 最终的式子中含有 几种因素,所以考虑将他们都塞进矩阵。(由于 数字太多不便展示,下文将用 的例子示范,即 的矩阵)。
现在通过计算,考虑了 的情况,思考该矩阵应乘上什么得到新矩阵。
事实上,可以这样(矩阵行列从 编号):
注: 并不是放在第 行,而是放在第 行的第 个位置,所以,图中的 ,即为下图:
我们来模拟一下:
第一行乘第一列,得到:
第一行乘第二列,得到:
第一行乘第三列,同上。
所以我们设置第 个字母的矩阵为:
注意其中 的位置。
注意矩阵乘法的顺序,不满足交换律,一定是从后向前乘。
题目还要求所有连续 个的答案,可以将序列分成若干长度为 的块,对于每个块,维护前缀积和后缀积,那么一段长度为 的区间就可以用前缀和后缀拼出来,注意乘的顺序。
时间复杂度:
代码:
#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; } /* */
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!