Edu 169 补题记录

F. Make a Palindrome

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

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

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

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

问题分析:

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

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

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

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

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

既然只有合并,考虑区间 dp,设 fi,j 表示 ij 操作的最少次数,那么枚举要合并的两个端点 il,rj,即我们希望将 il1r+1j 范围内的数字分别合并然后相等消去,即满足如下条件(设 si 为前缀和):

sl1si1=sjsrsi1+sj=sl1+sr

那么有如下转移:

fi,j=min{fl,r+(l1i)+(jr1)}fi,j=min{fl,r+(lr)+ji2}

直接枚举时复 O(n4),无法通过,将满足条件按照上面式子转化,发现当我们枚举 i,j 时,等式左边已知,于是可以将等于所有 sl1+sr 对应的 fl,r+(lr) 丢进一个 map,维护满足条件的最小值。

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

代码:

#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 的操作,该字符串至少包含从 192 个数字,如下所示:

将其拆分为偶数个非空子字符串,让这些子字符串为 t1,t2,,tm ( t=t1+t2++tm ,其中 + 是连接操作);
先写入字符串 t2 t1 次,再写入字符串 t4 t3 次,以此类推。

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

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

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

问题分析:

矩阵妙妙题。

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

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

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

于是可以设计 dp,设 fi 表示考虑 in 的最优解,那么有:

fi=fj+1+ai×(ji)fi=(fj+1+ai×j)ai×i

观察到 ai[1,9],设 gi=fj+1+i×j,那么方程再次改写为:

fi=gaiai×i

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

由于 g,f 最终的式子中含有 i×j,gai,ai×i,fi 几种因素,所以考虑将他们都塞进矩阵。(由于 19 数字太多不便展示,下文将用 12 的例子示范,即 3×3 的矩阵)。

[fid+1g1g2]

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

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

[fid+1g1g2]×[id×1id×2aid×id00]

注:aid×id 并不是放在第 1 行,而是放在第 id 行的第 0 个位置,所以,图中的 id=1,即为下图:

[fid+1g1g2]×[id×1id×2a1×100]

我们来模拟一下:

第一行乘第一列,得到:fid=min{fid+1+,g1a1×1,g2×}=g1a1×1

第一行乘第二列,得到:g1=min{fid+1+id×1,g1+0,g2+}=min{g1,fid+1+id×1}

第一行乘第三列,同上。

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

[id×1id×2aid×id00]

注意其中 aid×id 的位置。

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

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

时间复杂度: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 @   2017BeiJiang  阅读(56)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示