ural 1996. Cipher Message 3 KMP+FFT

     长度为N,M的两个字符串,每个串的单位元素是一个8位的01串,现在允许修改第二个串的每个单位元素的最后一位(0变1或者1变0),问最少要修改几个字符可以使得第二个串在第一个串中匹配,并输出最靠前的匹配位置。

      首先把单位元素分成前七位和最后一位两部分,前七位可以看成一个0-127的数存起来,得到前七位组成两个串A,B和最后一位组成的两个串a,b。然后对A,B跑一边KMP,剩下的就是在匹配的位置中,找一个对应位置a,b的最小海明距离。N,M最大都是250000,暴力找显然要超..直接拿a,b乘起来得到的结果好像也没什么规律...但是如果把b串翻转后,再把a,b相乘,得到的就是从b[m-1]=a[0]开始到b[0]=a[n-1],从b[0]--b[m-1]在对应的a上共有多少个共同的1,至于为什么,在草纸上列出相乘的竖式,就会发现一位一位乘出m行积后在相加时,对应的每一列其实就是b翻转前同a中相应位置的字符&运算和的和,然后把a,b取反,再乘一边就能得到每位开始共同的0的个数,之后枚举下kmp的得到的答案,找一下相同的1+相同的0的最大值的位置就行了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
using namespace std;
typedef long long ll;
vector<int> ans;

const double PI = acos(-1.0);
struct comp
{
    double r,i;
    comp(double rt=0,double it=0)
    {
        r=rt;
        i=it;
    }
    comp operator +(const comp& b)
    {
        return comp(r+b.r,i+b.i);
    }
    comp operator -(const comp &b)
    {
        return comp(r-b.r,i-b.i);
    }
    comp operator *(const comp &b)
    {
        return comp(r*b.r-i*b.i,r*b.i+i*b.r);
    }
};

void change(comp y[],int len)//二进制转置--雷德算法
{
    int i,j,k;
    for(i = 1, j = len/2;i < len-1;i++)
    {
        if(i < j)swap(y[i],y[j]);
        k = len/2;
        while( j >= k)
        {
            j -= k;
            k /= 2;
        }
        if(j < k)j += k;
    }
}

void fft(comp y[],int len,int on)
/* on=1 DFT 把一个多项式的系数向量转化为点集表示;
on=-1,IDFT 把一个点集转化成多项式的系数向量*/
{
    change(y,len);
    for(int h = 2;h <= len;h <<= 1)
    {
        comp wn(cos(-on*2*PI/h),sin(-on*2*PI/h));
        for(int j = 0;j < len;j += h)
        {
            comp w(1,0);
            for(int k = j;k < j+h/2;k++)
            {
                comp u = y[k];
                comp t = w*y[k+h/2];
                y[k] = u+t;
                y[k+h/2] = u-t;
                w = w*wn;
            }
        }
    }
    if(on == -1)
        for(int i = 0;i < len;i++)
            y[i].r /= len;
}

const int maxn=8*250010;
int n,m,k,p,q;
int s1[255000],s2[255010];
char a[250010],b[250010];
char ss[20];
int fail[maxn];
comp x[610000],y[610000],z[610000];
int zero[610000],one[610000];

void getFail(int* P,int* f)
{

    f[0]=0;
    f[1]=0;
    for (int i=1; i<m; i++)
    {
        int j=f[i];
        while(j && P[i]!=P[j]) j=f[j];
        f[i+1]=P[i]==P[j]?j+1:0;
    }
}
int find(int* T,int* P,int* f)
{

    getFail(P,f);
    int j=0;
    for (int i=0; i<n; i++)
    {
        while(j && P[j]!=T[i]) j=f[j];
        if (P[j]==T[i]) j++;
        if (j==m) ans.push_back(i-m+1);
    }
}


int main()
{
//    freopen("in.txt","r",stdin);
    scanf("%d%d",&n,&m);
    ans.clear();
    for (int i=0; i<n; i++)
    {
        scanf("%s",ss);
        int tp=0;
        a[i]=ss[7];
        for (int j=0; j<7; j++)
        if (ss[j]-'0') tp|=(1<<j);
        s1[i]=tp;
    }
    a[n]='\0';
    for (int i=0; i<m; i++)
    {
        scanf("%s",&ss);
        int tp=0;
        b[i]=ss[7];
        for (int j=0; j<7; j++)
        if (ss[j]-'0') tp|=(1<<j);
        s2[i]=tp;
    }
    b[m]='\0';

    memset(fail,0,sizeof fail);
    find(s1,s2,fail);
    if (ans.size()==0)
    {
        cout<<"No"<<endl;
        return 0;
    }
    for (int i=0; i<(m>>1); i++)
    swap(b[i],b[m-i-1]);

    int len1=max(n,m);
    int len=1;
    while(len<2*len1) len<<=1;

    for (int i=0; i<n; i++)
    x[i]=comp(a[i]-'0',0);
    for (int i=n; i<len; i++)
    x[i]=comp(0,0);

    for (int i=0; i<m; i++)
    y[i]=comp(b[i]-'0',0);
    for (int i=m; i<len; i++)
    y[i]=comp(0,0);

    fft(x,len,1);
    fft(y,len,1);
    for (int i=0; i<len; i++)
    z[i]=x[i]*y[i];
    fft(z,len,-1);

    for (int i=0; i<len; i++)
    one[i]=(int)(z[i].r+0.5);

    for (int i=0; i<n; i++)
    {
        if (a[i]-'0') x[i]=comp(0,0);
        else x[i]=comp(1,0);
    }
    for (int i=n; i<len; i++)
    x[i]=comp(0,0);

    for (int i=0; i<m; i++)
    {
        if (b[i]-'0') y[i]=comp(0,0);
        else y[i]=comp(1,0);
    }
    for (int i=m; i<len; i++)
    y[i]=comp(0,0);

    fft(x,len,1);
    fft(y,len,1);
    for (int i=0; i<len; i++)
    z[i]=x[i]*y[i];
    fft(z,len,-1);
    for (int i=0; i<len; i++)
    zero[i]=(int)(z[i].r+0.5);

    for (int i=m-1; i<len; i++)
    {
        one[i-(m-1)]=one[i];
        zero[i-(m-1)]=zero[i];
    }
    if(ans.size())
    {
    cout<<"Yes"<<endl;
    int ans1=99999999,ans2=0;
    for (int i=0; i<ans.size(); i++)
    if (m-one[ans[i]]-zero[ans[i]]<ans1)
    {
        ans1=m-one[ans[i]]-zero[ans[i]];
        ans2=ans[i];
    }
    cout<<ans1<<" "<<ans2+1<<endl;
    }
    else
    {
        cout<<"No"<<endl;
    }


    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值