[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); }
相关文章推荐
- CSS 3 伪类选择器
- 解决对话框报错方案
- chrome的广告过滤插件下载地址
- php使用ffmpeg向视频中添加文字字幕
- Unsupported major.minor version 52.0
- php使用ffmpeg向视频中添加文字字幕
- 验证码的发展史
- PPT1 例2
- HZAU 1018 Catching Dogs
- CSS 3 属性选择器
- 第10、11周项目1- 点-圆-圆柱类族的设计(1)
- isight整数规划问题求解,用most算法
- 接口测试及服务器性能压测
- POJ 3254 Corn Fields 状压DP
- linux install g++
- 软件工程个人作业08
- 【OK210试用体验】进阶篇(2)视频图像采集之MJPG-streamer编译(arm移植)
- 算法_记忆化搜索DFS_地宫取宝
- 在O(1)时间删除链表结点
- 42.扑克牌顺子(快速排序)