zoukankan      html  css  js  c++  java
  • [hdu5307] He is Flying [FFT+数学推导]

    题面

    传送门

    思路

    看到这道题,我的第一想法是前缀和瞎搞,说不定能$Oleft(n ight)$?

    事实证明我的确是瞎扯......

    题目中的提示

    这道题的数据中告诉了我们:

    $sumleft(sleft[i ight] ight)<=50000$

    也就是说,总长度是很小的,这提示我们往“通过长度来解题”的方向上想

    那么,最便捷的处理区间长度和的算法是什么呢?前缀和

    我们需要求什么?

    做题的过程中,一定不能忘记这一点:我们要求的是,所有长度为s的区间的愉悦值总和

    那么,一个长度为s的区间,在关于长度的前缀和pre中是怎么表示的呢?

    就是所有的$pre[i]-pre[i-s]$,也就是它们下标之差是一定的

    下标差一定......不同的决策之和......

    FFT卷积!

    FFT

    我们考虑一个前缀和数组$pre$,范围从$0$到$sum(s[i])$,将它反向成为$antipre$,那么有:

    $pre[i]=antipre[n-i+1]$

    显然,当我们把$pre[i]$作为一个多项式$A$的$x{i+1}$项的系数,把$antipre[i]$作为另一个多项式$B$的$x{i+1}$项的系数时,将两个多项式乘起来,那么得到的新多项式$C$的第$x^{i+2}$项的系数,就保存了关于所有长度为i+1的区间的信息

    保存什么信息?

    题目中描述了,可能有区间的长度为0,也就是说,前缀和中的同一个长度数值上可能有多个点

    我们设前缀和长度为s的一共有$tot[s]$个点,最左边的是$l[s]$,最右边的是$r[s]$

    那么对于一段长度为s的,我们的信息来自于$pre[i]$和$pre[i-s]$

    假设我们当前要求rr-s这个长度和rr这个长度的信息,那么我们来考虑一下它们总共的贡献值是多少

    $l1,r1$就是$l[rr-s]$和$r[rr-s]$,$l2,r2$同理

    那么总的愉悦值贡献$contribution$应该是这样的:

    $contribution=sum_{i=l1}{r1}sum_{j=l2}{r2}left(j-i+1 ight)$

    $contribution=pq+sum_{j=l2}{r2}jp-sum_{i=l1}{r1}iq$

    $contribution=pq+frac{pqleft(r2+l2 ight)}2-frac{qpleft(l1+r1 ight)}2$

    $contribution=frac 12pqleft(l2-l1+r2-r1+2 ight)$

    因为前缀和求区间$[l,r]$是减掉第l-1项的,所以上式中的$pq$项抵消掉了

    因此

    $contribution=frac 12pqleft(l2-l1+r2-r1 ight)$

    这个东西显然可以两次FFT相减来处理

    总结

    本题到了这里就结束了

    纵观整个过程,我们从题目要求的东西,想到使用前缀和,又从需要对前缀和处理什么得到使用FFT的方法,最后用数学推导得到了FFT的方法

    换句话说,整个解题过程中我们的每一步操作都是有迹可循、有原因的,不是因为“我见过这种题所以用FFT”——总会有你没见过的题出现

    解题的过程、建模的过程是一个不断修正矛盾(问题)的过程,这个矛盾既可以是时间矛盾、正确性矛盾,也可以是代码编写复杂度的矛盾,但是核心的一点就是:矛盾的修正要有方向性、有依据

    一定是因为这里不对了我才去优化,而不是一上来就瞎猜一个高级方法然后硬想,那样想破了脑袋都想不出来

    Code:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #define ll long long
    using namespace std;
    inline ll read(){
        ll re=0,flag=1;char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-') flag=-1;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9') re=(re<<1ll)+(re<<3ll)+ch-'0',ch=getchar();
        return re*flag;
    }
    struct complex{
        long double x,y;
        complex(long double xx=0,long double yy=0){x=xx;y=yy;}
        complex operator +(const complex &b){return complex(x+b.x,y+b.y);}
        complex operator -(const complex &b){return complex(x-b.x,y-b.y);}
        complex operator *(const complex &b){return complex(x*b.x-y*b.y,x*b.y+y*b.x);}
    }A[300010],B[300010];
    const long double pi=acos(-1.0);
    ll n,m,limit=1,cnt=0,r[300010];
    void fft(complex *a,long double type){
        ll i,j,k,mid;complex x,y,wn,w;
        for(i=0;i<limit;i++) if(i<r[i]) swap(a[i],a[r[i]]);
        for(mid=1;mid<limit;mid<<=1ll){
            wn=complex(cos(pi/mid),type*sin(pi/mid));
            for(j=0;j<limit;j+=(mid<<1ll)){
                w=complex(1,0);
                for(k=0;k<mid;k++,w=w*wn){
                    x=a[j+k];y=a[j+k+mid]*w;
                    a[j+k]=x+y;a[j+k+mid]=x-y;
                }
            }
        }
        if(type==-1) for(i=0;i<=limit;i++) a[i].x=(ll)(a[i].x/limit+0.1); 
    }
    ll tot[50010],s[100010],L[100010],R[100010];
    ll ans[100010];
    int main(){
        ll T=read(),i,j,sum,tmp;
        while(T--){
            n=read();sum=0;memset(ans,0,sizeof(ans));memset(tot,0,sizeof(tot));tot[0]=1;
            memset(L,127,sizeof(L));memset(R,0,sizeof(R));L[0]=0;memset(s,0,sizeof(s));
            for(i=1;i<=n;i++){
                s[i]=read(),tot[sum+=s[i]]++;
                L[sum]=min(L[sum],i);R[sum]=max(R[sum],i);
            }
            tmp=0;
            for(i=1;i<=n;i++){
                if(s[i]) tmp=0;
                else tmp++,ans[0]+=(ll)(tmp*(tmp+1)/2ll);
            }//0要特殊处理,我们的算法并不能得到零的解,这一点具体是为什么,可以自己推导一下,会发现最后所有0的项的解都自我抵消掉了
            
            memset(r,0,sizeof(r));
            while(limit<=(sum<<1)) limit<<=1,cnt++;
            for(i=0;i<limit;i++) r[i]=((r[i>>1]>>1)|((i&1)<<(cnt-1)));
            
            memset(A,0,sizeof(A));memset(B,0,sizeof(B));
            for(i=0;i<=sum;i++) A[i+1].x=tot[i]*(L[i]+R[i])/2.0,B[i+1].x=tot[sum-i];//减去l1r1
            fft(A,1);fft(B,1);
            for(i=0;i<=limit;i++) A[i]=A[i]*B[i];
            fft(A,-1);
            for(i=1;i<=sum;i++) ans[i]-=(ll)A[sum+2-i].x;
            
            memset(A,0,sizeof(A));memset(B,0,sizeof(B));
            for(i=0;i<=sum;i++) A[i+1].x=tot[i],B[i+1].x=tot[sum-i]*(L[sum-i]+R[sum-i])/2.0;//加上r2l2
            fft(A,1);fft(B,1);
            for(i=0;i<=limit;i++) A[i]=A[i]*B[i];
            fft(A,-1);
            for(i=1;i<=sum;i++) ans[i]+=(ll)A[sum+2-i].x;
            
            //整个过程中,因为我们分析的公式是针对区间[l,r]的l和r的,但是算法实现的时候是针对l-1和r的(前缀和思想),所以原式中要去掉那个p*q
            
            for(i=0;i<=sum;i++) printf("%lld
    ",ans[i]);
        }
    }
    
  • 相关阅读:
    WiFi流量劫持—— 浏览任意页面即可中毒!
    POJ3614防晒霜 这个贪心有点东西(贪心+优先队列)
    7月24日训练记录
    环形均分纸牌问题(中位数)
    7月23日训练总结
    7.22学习总结
    POJ 1176 Party Lamps&& USACO 2.2 派对灯(搜索)
    尘埃落定,以梦为马,不负韶华
    P1522 牛的旅行 Cow Tours(floyd)
    分享一种解题的思想,有关时间复杂度探讨
  • 原文地址:https://www.cnblogs.com/dedicatus545/p/8846564.html
Copyright © 2011-2022 走看看