UVALive-3716 DNA Regions

题目大意:

有两个字符串, 现在要你选择一个区间[ l , r ], 要求这个区间内部两个串不相同的位置小于等于p%, 求这个区间最长是多长.

设dp[ i ]表示以i为结尾的满足条件的最长串长度是多少.

然后我们观察一下转移条件, 我们可以发现, 需要满足的条件是(sum[ i ]-sum[ j ])/( i-j )<=p/100, 其中sum表示不相同的位置的前缀和.

日常化式子就可以得到sum[ i ]*100-p*i<=sum[ j ]*100-p*j, 所以我们需要的就是满足条件的最小的j.

那么我们把这个值记为f, 丢到一个数组( 其实这个数组就像是一个单调栈, 但是单调性维护不一样 )里面.

如果当前i的f[ i ]大于数组最后一个( 可以理解为栈顶 ), 就直接把j压进去, 因为不存在f[ j ]比f[ i ]大了,这样就保证了这个数组( 单调栈 )是按f递增的;

如果小于最后一个, 就在存的这个数组中二分查找大于f[ i ]的最小的f[ j ].

由于我们放进去是从前往后放的, 所以数组中的f[ j ]对应的j也是递增的, 选择最小的f[ j ]就是最小的j, 此时长度也是最长的, 然后dp[ i ]=i-j.

查找了就不用再压到数组里面去了.

因为后面的是查找最小的j满足f[ i ]<=f[ j ], 而当前i进行二分查找就说明数组中存了比当前f[ i ]大的f[ j ], 且这个j在当前i前面, 如果后面有一个i'满足f[ i' ]<=f[ i ], 那么也满足f[ i' ]<=f[ j ], 且i和i'的距离要比j和i'的距离远, 所以直接弃掉当前的f[ i ]没问题.

其实有许多东西都是可以不用开的, 比方说sum[ i ]和dp[ i ], 直接用变量记下来就可以了.

实时对ans取min.

代码如下:

//made by Crazy01
#include<bits/stdc++.h>
#define inf 1<<30
#define ll long long
#define db double
#define c233 cout<<"233"<<endl
#define mem(s) memset(s,0,sizeof(s))
const int N=150050;
using namespace std;

char a[N],b[N];
int f[N];
int n,p,ans,sum;
struct lll{
  int q[N];
  int tp;
  void clear(){q[0]=0; tp=0;}
  bool empty(){return tp==0;}
  void push(int x){q[++tp]=x;}
  bool ck(int x){return f[q[tp]]<f[x];}
  int lyb(int x){
    int l=0,r=tp;
    while(l<=r){
      int mid=(l+r)>>1;
      if(f[q[mid]]<f[x])l=mid+1;
      else r=mid-1;
    }
    return q[l];
  }
}q;

inline int gi(){
  int x=0,res=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
  while(ch<='9'&&ch>='0')x=(x<<1)+(x<<3)+ch-48,ch=getchar();
  return x*res;
}

void init(){
  p=gi();
  scanf("%s",a+1);
  scanf("%s",b+1);
}

void work(){
  ans=sum=0;
  for(int i=1;i<=n;i++){
    sum+=(a[i]!=b[i]);
    f[i]=100*sum-p*i;
    if(q.ck(i)){q.push(i);continue;}
    ans=max(ans,i-q.lyb(i));
  }
  if(ans)printf("%d
",ans);
  else printf("No solution.
");
}

int main(){
  n=gi();
  while(n){
    q.clear();
    init();
    work();
    n=gi();
  }
  return 0;
}
原文地址:https://www.cnblogs.com/Crazy01/p/7700079.html