Edu 169 补题记录
F. Make a Palindrome
给定一个由 \(n\) 个整数组成的数组 \(a\) 。
让函数 \(f(b)\) 返回使数组 \(b\) 成为回文所需的最小操作次数。您可以进行的操作有:
- 选择两个相邻的元素 \(b_i\) 和 \(b_{i+1}\) ,删除它们,并用一个元素 \((b_i + b_{i + 1})\) 替换它们;
- 或者选择一个元素 \(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\) 为前缀和):
那么有如下转移:
直接枚举时复 \(\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\) 的最优解,那么有:
观察到 \(a_i\in [1,9]\),设 \(g_{i}=f_{j+1}+i\times j\),那么方程再次改写为:
考虑用矩阵维护。注:此处矩阵乘法的符号并不是 \(\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\) 的矩阵)。
现在通过计算,考虑了 \(id+1\sim n\) 的情况,思考该矩阵应乘上什么得到新矩阵。
事实上,可以这样(矩阵行列从 \(0\) 编号):
注:\(-a_{id}\times id\) 并不是放在第 \(1\) 行,而是放在第 \(id\) 行的第 \(0\) 个位置,所以,图中的 \(id=1\),即为下图:
我们来模拟一下:
第一行乘第一列,得到:\(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\) 个字母的矩阵为:
注意其中 \(-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;
}
/*
*/