• FFT与一些冷门问题


    FFT也能用于一些特殊的字符串匹配与最小化问题。

    Prob 1 : 给出模式串A与文本串B,两个串中只有26个大写字母与通配符'?'(即可以任意匹配一个字符),求A在B中的匹配数。要求以FFT为例给出上限为O(nlogn)的算法。


    Prob 2 : 给出模式串A与文本串B,字符集很小,求A在B中的匹配数,允许有k个字符不同。要求以FFT为例给出上限为O(nlogn*|S|)的算法。


    Prob 3 : 给出数列a和b,长度均为n,a可以顺时针转动但不能翻转,最小化sigma(ai*bi)。要求以FFT为例给出上限为O(nlogn)的算法。


    不知道是什么东西的引导

     我们先看看FFT干了什么,就是个卷积。

    以数组a和b为例(这里下标从1开始),a有4位,b有8位,卷出的结果放在c数组中。

    然而并没有什么用处。我们再往后看几位:

    虽然FFT时会把a数组给自动补全,但从实际意义上来讲,只是整个a数组与b数组中四个数相乘放进c中。

    不难发现,此时的下标就是一个“占位符”。

    我们顺便把a数组反一反,就有:

    这样就有很好的性质了,c数组中从第5位开始,每往后一位就是整个a数组与b数组中连续的四位积的和。

    同样可以拓展到更大的数组中,接下来的题目就要利用这个特点。


    Prob 1

    我们发现字符串的匹配很类似于上述图片中一位位算过去。

    先不考虑通配符,只是普通的字符串匹配。定义为A的第x位与B的第y位的匹配度。若C为0,则是匹配的。

    再定义,表示B字符串中以x为结尾,向前m-1位与A字符串的匹配度。我们天真地考虑若P为0,则是匹配的。

    但是C有正有负,因此一旦连续的几位的可重集是相同的,P的结果就为0。

    所以在C上动手脚。干脆加个平方吧:

    这样,

    但还不能优化!因此我们又看了看上面的图,把A字符串反了过来。定义

    注意到(m-i-1)+(x-m+1+i)==x,有:

    这样S与B做一遍卷积就行了。S与B的值取字符串的字符值就行了。

    那带上通配符,只要有任何字符遇上“?”,C的值就必须是0。这样在原来P的式子中,后面乘上S与B中相应的第几位,若是“?”,给其赋值为0。则

    做三次FFT,加起来等于0的,即为匹配。

      1 //源:https://www.luogu.org/problemnew/show/P4173
      2 #include<bits/stdc++.h>
      3 using namespace std;
      4 const int maxn=1233333;
      5 const double pi=3.1415926535898;
      6 struct com
      7 {
      8     double a,b;
      9     com(double A=0,double B=0){a=A,b=B;}
     10     void operator=(com x){a=x.a,b=x.b;}
     11     com operator+(com x){return com(a+x.a,b+x.b);}
     12     com operator-(com x){return com(a-x.a,b-x.b);}
     13     com operator*(com x){return com(a*x.a-b*x.b,a*x.b+b*x.a);}
     14     com operator/(double d){return com(a/d,b/d);}
     15     com operator*(double d){return com(a*d,b*d);}
     16 }A[maxn],B[maxn],ans[maxn];
     17 int n,m,limit,r[maxn],len,g1[maxn],g2[maxn];
     18 char ch;
     19 int re(int x)
     20 {
     21     int sum=0;
     22     for(int i=0;i<len;++i)
     23     {
     24         sum=sum*2+x%2;
     25         x/=2;
     26     }
     27     return sum;
     28 }
     29 void FFT(com*A,int g)
     30 {
     31     for(int i=0;i<limit;++i)
     32         if(i<r[i])swap(A[i],A[r[i]]);
     33     for(int i=2;i<=limit;i*=2)
     34     {
     35         com w(cos(2*pi/i),g*sin(2*pi/i));
     36         for(int j=0;j<limit/i;++j)
     37         {
     38             com d(1,0);
     39             for(int k=0;k<i/2;++k)
     40             {
     41                 com a=A[i*j+k],b=d*A[i*j+i/2+k];
     42                 A[i*j+k]=a+b;
     43                 A[i*j+i/2+k]=a-b;
     44                 d=w*d;
     45             }
     46         }
     47     }
     48 }
     49 void out(com*A)
     50 {
     51     for(int i=0;i<limit;++i)cout<<A[i].a<<' ';
     52     cout<<endl;
     53 }
     54 void get(com*A,com*B)
     55 {
     56     FFT(A,1);
     57     FFT(B,1);
     58     for(int i=0;i<limit;++i)A[i]=A[i]*B[i];
     59     FFT(A,-1);
     60     for(int i=0;i<limit;++i)A[i]=A[i]/limit;
     61 }
     62 int main()
     63 {
     64     ios::sync_with_stdio(false);
     65     cin>>n>>m;
     66     for(int i=n-1;i>=0;--i)
     67     {
     68         cin>>ch;
     69         if(ch!='*')
     70         {
     71             int x=ch-'a'+1;
     72             A[i]=g1[i]=x;
     73         }
     74     }
     75     for(int i=0;i<m;++i)
     76     {
     77         cin>>ch;
     78         if(ch!='*')
     79         {
     80             int x=ch-'a'+1;
     81             g2[i]=x;
     82             B[i]=x*x*x;
     83         }
     84     }
     85     limit=1;
     86     while(limit<n+m+1)limit*=2,++len;
     87     for(int i=0;i<limit;++i)r[i]=re(i);
     88     get(A,B);
     89     for(int i=0;i<limit;++i)ans[i]=A[i];
     90     
     91     for(int i=limit-1;i>=0;--i)A[i]=g1[i]*g1[i]*g1[i];
     92     for(int i=0;i<limit;++i)B[i]=g2[i];
     93     get(A,B);
     94     for(int i=0;i<limit;++i)ans[i]=ans[i]+A[i];
     95     
     96     for(int i=limit-1;i>=0;--i)A[i]=g1[i]*g1[i];
     97     for(int i=0;i<limit;++i)B[i]=g2[i]*g2[i];
     98     get(A,B);
     99     for(int i=0;i<limit;++i)ans[i]=ans[i]-A[i]*2;
    100     
    101     int tot=0;
    102     for(int i=n-1;i<m;++i)if(int(ans[i].a+0.5)==0)++tot;
    103     cout<<tot<<endl;
    104     for(int i=n-1;i<m;++i)if(int(ans[i].a+0.5)==0)cout<<i-n+2<<' ';
    105     cout<<endl;
    106     return 0;
    107 }
    代码

    Prob 2

    若字符只有’0'和'1'的呢?按照上面的做法,最后结果小于等于2的即为匹配(因为会有地方算两遍)。

    再拓展一下,字符集多大就做几遍。最后的和加起来即可。

    但由于一些奇妙的原因,至今我交不过去。只有网址。

    其实随便哈希就能过了,SA也行。

    https://www.luogu.org/problemnew/show/P3763


    Prob 3

    仍然是老套路。我们只要把其中某个数组的长度变为两倍,再重复写下前面的数就行了。

    类似的题目:https://www.luogu.org/problemnew/show/P3723

    最后,如果能用一些数据结构或方法来维护的话就别写FFT了。

  • 相关阅读:
    JAX XML 实例
    javascript获取浏览器的全部信息
    如何防止ASP.NET应用程序中的SQL注入安全漏洞
    测试Web应用程序是否存在跨站点脚本漏洞()
    C# 中的委托和事件详解(四)
    C# 中的委托和事件详解(一)
    远程启动SSIS包
    AX2012学习笔记Date Effectiveness(有效期间)
    AX2010学习笔记Surrogate Key(代理键)
    SSRS 2005 400 Bad Request
  • 原文地址:https://www.cnblogs.com/GreenDuck/p/10585871.html
Copyright © 2020-2023  润新知