您的位置:首页 > 其它

[NOI2016模拟5.14]最长公共子序列

2016-05-16 19:49 351 查看

前言

关于这个前缀……是因为这个题目的名字很容易让人想到一些别的题目,所以加前缀区分一下。

题目描述



naive做法

设i表示A串不在LCS的位置,j表示B串不在LCS的位置。由于不能让lcs结果为n,所以填入B串第j个位置的字符不能为s[j]。

我们先固定j,然后考虑B串的可能性个数。

考虑j<=i的情况。



第一条红线前的部分就被确定了,只需要考虑第一条红线后有多少种可能即可。

那么就是看i取合法值时能造成的不同B串个数。

例如一个i,假如存在一个i′<i,能造成相同的B串。



除两条红线以内的部分一定相同。现在红线以内的部分也相同,那么表明s[i’..i-1]=s[i’+1..i]也就是说s[i-1]=s[i]。

因此就很简单了,合法的i一定满足s[i-1]!=s[i]。然后枚举j并统计j之后有多少合法i,假设为t个,那么此时贡献为(m-1)*t,因为第j位有m-1种填法。

对于i<=j也同理,正着做一遍即可。然后减去i=j的情况。

代码

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=100000+10;
char a[maxn];
int i,j,k,l,t,n,m,cnt;
ll ans;
char get(){
char ch=getchar();
while (ch<'a'||ch>'z') ch=getchar();
return ch;
}
int main(){
scanf("%d%d",&n,&m);
fo(i,1,n) a[i]=get();
cnt=1;
fd(i,n,1){
if (i<n&&a[i]!=a[i+1]) cnt++;
ans+=(ll)(m-1)*cnt;
}
cnt=1;
fo(i,1,n){
if (i>1&&a[i]!=a[i-1]) cnt++;
ans+=(ll)(m-1)*cnt;
}
ans-=(ll)(m-1)*n;
printf("%lld\n",ans);
}


这个做法为什么会naive的拿了0分呢?

去重

因为naive做法没有考虑到当j不同时也可能造出一样的B串。

一个简单的反例是ab。

接下来,我们来定义一种合法的方案:

1、去掉A串第i位与B串第j位后完全相同。

2、s[i]!=s[i+1]。(这条性质是因为如果满足这个会存在i’与i重复,具体看上一个部分)

3、B串第j位填入的字符不为s[j]。

4、用(i,j)来表示这种方案,暂不考虑你在B串第j位到底填了什么。

然后我们假设当前有两种合法方案重了,分别为(i’,i)和(j’,j),对应的B串分别为B1和B2。让我们来探究一下吧。

为了方便,我们设i<=j。

接下来我们来证明一些东西,下面列举的是不可能的结论。

1、j’>i。

那么B2的第i位必然是s[i],而根据性质3我们知道B1的第i位必然不是s[i]。这种情况下B1与B2不可能相同。

同理我们知道了i′>j也是不可能的。

2、i′<=i



因为i′<i所以红线后的部分是s[i+1..n]

因为j′<=i<=j即j′<=j所以橙线后的部分是s[j+1..n]

因为B1和B2要相同所以蓝线后的部分相同。

有s[j..n]=B2第j位+s[j+1..n]

那么B2第j位即为s[j],违背了性质3。

因此i<i′

因此我们知道j′<=i<i′<=j

3、j′<i。



因为i<i′,所以我们可以知道B1在蓝线前的部分是s[1..i-1]

因为j′<=j,所以我们可以知道B2在蓝线前的部分是s[1..j’-1]+s[j’+1..i]

那么我们知道s[1..i-1]=s[1..j’-1]+s[j’+1..i]

也就是s[j’..i-1]=s[j’+1..i]

那么就有s[j’]=s[j’+1],这与性质2产生了矛盾。

因此我们得到了j’=i,同理有i’=j。

然后就得到了出现重时只能是哪种情况。

接着我们设(i,j)和与(j,i)重,现在就是探究重的条件。设i<=j。



然后我们知道B2的第i位为s[i+1],因为B1与B2相同,B1的第i位是s[i+1],而B1的第i位也不能是s[i]。当s[i]=s[i+1]时,B1第i位一定不是s[i],也就一定不是s[i+1],那么B1与B2就不相同,所以不符合要求。因此s[i]要不等于s[i+1]。

然后,红线前的部分和蓝线后的部分肯定相同。

考虑橙线到紫线这一段相同,即s[i..j-2]=s[i+2..j]

那么就有

s[i]=s[i+2]

s[i+1]=s[i+3]

……

s[j-2]=s[j]

因此我们得到(i,j)会被算重的条件:

1、s[i]!=s[i+1]

2、s[i..j]是一个形如ababababa……这样的字符串。

因此,我们只需要预处理right[i]表示每次往后跳两格,跳到的必须与本身相同跳到的最远点。

枚举i,然后便可以得到最大j,那么s[i..j]中任一前缀都会被算重。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=100000+10;
char a[maxn];
int right[maxn];
int i,j,k,l,t,n,m,cnt;
ll ans;
char get(){
char ch=getchar();
while (ch<'a'||ch>'z') ch=getchar();
return ch;
}
int main(){
scanf("%d%d",&n,&m);
fo(i,1,n) a[i]=get();
cnt=1;
fd(i,n,1){
if (i<n&&a[i]!=a[i+1]) cnt++;
ans+=(ll)(m-1)*cnt;
}
cnt=1;
fo(i,1,n){
if (i>1&&a[i]!=a[i-1]) cnt++;
ans+=(ll)(m-1)*cnt;
}
ans-=(ll)(m-1)*n;
fo(i,1,n) right[i]=i;
fd(i,n-2,1)
if (a[i]==a[i+2]) right[i]=right[i+2];
fo(i,1,n-1){
if (a[i]==a[i+1]) continue;
j=min(right[i],right[i+1])+1;
ans-=(ll)(j-i);
}
printf("%lld\n",ans);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: