多校第二场1008 He is flying


题目描述:

1≤n≤100000 n个区间,每个区间有一个长度.,也可以看做权值. n个区间是按照实际的顺序给的. 现在一个人,要连续的跑这些区间,跑j-i+1个区间的话,会获得j-i+1的hp值,这个区间长度为s[i]+到+s[j]. 限定给出的所有的s的和0≤s≤50000. 问跑长度为0的段段的所有可能情况的hp值的和,长度为1的…长度为s的 都要输出. 注意s[i]可能为0.

题解:

1e5的区间数不能枚举左右端点.我们发现有连续这个要求,因此想到分治.
法一:分治.
左边递归,右边递归. 只剩跨过中间的值. 我们向左统计区间长度是i的种类数,放在numl中,向右统计长度是i的种类数,放在numr中.现在要算出所有的区间长度的每一个区间长度的hp和. 是经典的加扩展的fft算. 左边l,右边r.要算l+r的情况数. 但是本题不是情况数,而是有hp值的加权. 其实是一个经典的问题.我们再处理左边的长度是i的总的hp值放在hpl中,那么这些值对答案的贡献要乘上他们的情况数. 其实就是hpl和numr做fft. 同理 hpr和numl做fft. 这样就算出来了.
但是分治有两个重点:(1)不是找中点,因为fft的复杂度和他们的最大值s的和值有关,所以找尽量均分s的点作为分治点.
(2)如果s均是0,那么我们每次只会去掉一个.这样是不好的.这样的情况特判或者取中点就好了.

法二:写出来母函数, 之后用fft算. 母函数把hp值看作两次求. 先定为 i到j的hp值为j. 这样写出母函数. 然后再减去母函数 i到jhp值为i-1. 这样就弄好了hp值(因为每种情况的个数两次算法都一样).

重点:

法一:(1)连续的区间想分治.
(2)经典的fft拓展算权值.
(3)分治不看mid,根据fft划分.
(4)分析出000的情况.特判
法二:母函数写带权值只能带一个数,将hp值重新定义

代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <ctype.h>
#include <limits.h>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
#include <stack>
#include <set>
#include <bitset>
#define CLR(a) memset(a, 0, sizeof(a))
#define REP(i, a, b) for(ll i = a;i < b;i++)
#define REP_D(i, a, b) for(ll i = a;i <= b;i++)

typedef long long ll;

using namespace std;

int t;

const int maxn = 2e5 + 100;
ll sum[maxn];
ll s[maxn], n, ans[maxn];
ll l[maxn], r[maxn];
ll lhp[maxn], rhp[maxn];


const long double PI = acos(-1.0);
//复数结构体
struct Complex
{
    long double x,y;//实部和虚部 x+yi
    Complex(long double _x = 0.0,long double _y = 0.0)
    {
        x = _x;
        y = _y;
    }
    Complex operator -(const Complex &b)const
    {
        return Complex(x-b.x,y-b.y);
    }
    Complex operator +(const Complex &b)const
    {
        return Complex(x+b.x,y+b.y);
    }
    Complex operator *(const Complex &b)const
    {
        return Complex(x*b.x-y*b.y,x*b.y+y*b.x);
    }
};

Complex x1[maxn],x2[maxn];
/*
* 进行FFT和IFFT前的反转变换。
* 位置i和 (i二进制反转后位置)互换
* len必须去2的幂
*/
inline void change(Complex y[],ll len)
{
    ll i,j,k;
    for(i = 1, j = len/2; i <len-1; i++)
    {
        if(i < j)swap(y[i],y[j]);
//交换互为小标反转的元素,i<j保证交换一次
//i做正常的+1,j左反转类型的+1,始终保持i和j是反转的
        k = len/2;
        while(j >= k)
        {
            j -= k;
            k /= 2;
        }
        if(j < k)j += k;
    }
}
/*
* 做FFT
* len必须为2^k形式,
* on==1时是DFT,on==-1时是IDFT
*/
inline void fft(Complex y[],ll len,ll on)
{
    change(y,len);
    for(ll h = 2; h <= len; h <<= 1)
    {
        Complex wn(cos(-on*2*PI/h),sin(-on*2*PI/h));
        for(ll j = 0; j < len; j+=h)
        {
            Complex w(1,0);
            for(ll k = j; k < j+h/2; k++)
            {
                Complex u = y[k];
                Complex t = w*y[k+h/2];
                y[k] = u+t;
                y[k+h/2] = u-t;
                w = w*wn;
            }
        }
    }
    if(on == -1)
        for(ll i = 0; i < len; i++)
            y[i].x /= len;
}
inline void gao(ll l[], ll r[], ll longest)// longest ye youzhi 0ye you
{
    ll t = longest*2+1;
    ll len = 1;
    while(len < t)
    {
        len*=2;
    }
    for(ll i=0;i<=longest;i++)
    {
        x1[i] = Complex(l[i], 0);
        x2[i] = Complex(r[i], 0);
    }
    for(ll i = longest+1;i<len;i++)
    {
        x1[i] = Complex(0, 0);
        x2[i] = Complex(0, 0);
    }
    fft(x1, len, 1);
    fft(x2, len, 1);
    for(ll i = 0;i<len;i++)
    {
        x1[i] = x1[i]*x2[i];
    }
    fft(x1, len, -1);
    for(ll i = 0;i<len;i++)
    {
        long double temp = x1[i].x;

        ll realans = temp +0.5;
//        if(low==0&&high==1)
//        {
//            printf("%I64d    -- %I64d\n", i, realans);
//        }
        ans[i] += realans;
    }
}

ll getSum(int a, int b)
{
    if(a==0)
    {
        return sum[b];
    }
    return sum[b]-sum[a-1];
}

inline void f(ll low, ll high)
{
    if(low == high)
    {
        ans[s[low]]++;
        return;
    }
    if(low + 1 == high)
    {
        ans[s[low]]++;
        ans[s[high]]++;
        ans[s[low]+s[high]] += 2;
        return;
    }
    ll mid;
    ll tot = 0;
    for(int i = low;i<= high;i++)
    {
        tot += s[i];
    }
    mid = low;
    ll now = s[low];
    for(int i = low+1;i<= high - 1;i++)
    {
        now += s[i];
        if(now < tot/2)
        {
            mid = i;
        }
        else
        {
            break;
        }
    }
    if(tot == 0)
    {
        mid = (low+high)/2;
    }
    //mid = (low+high)/2-1;
    f(low, mid);
    f(mid+1, high);
    ll longest=0;
    for(int i = mid;i>=low;i--)
    {
        longest += s[i];
    }
    ll t = 0;
    for(ll i = mid+1;i<=high;i++)
    {
        t += s[i];
    }
    longest = max(longest, t);
    for(ll i = 0;i<=longest;i++)
    {
        l[i] = 0ll;
        r[i] = 0ll;
        lhp[i] = 0ll;
        rhp[i] = 0ll;
    }
    t = 0;
    for(ll i = mid;i>=low;i--)
    {
        t += s[i];
        l[t]++;
        lhp[t] += (mid-i+1);
    }
    t = 0;
    for(ll i = mid+1;i<=high;i++)
    {
        t += s[i];
        r[t]++;
        rhp[t] += (i - (mid+1)+1);
    }

    gao(l, rhp, longest);//0daolongest douyou zhi
    gao(lhp, r, longest);
}
inline void outPut(ll x)
{
    if(x==0)
    {
        return;
    }
    outPut(x/10);
    char ch = x%10 + '0';
    putchar(ch);
}
int main()
{
    //freopen("8Hin.txt", "r", stdin);
   // freopen("8Hout.txt", "w", stdout);
    scanf("%d", &t);
    while(t--)
    {
        ll output = 0;
        scanf("%I64d", &n);
        for(int i = 0;i< n;i++)
        {
            scanf("%I64d", &s[i]);
            output += s[i];
        }
        sum[0] = s[0];
        for(int i = 0;i<n;i++)
        {
            sum[i] = sum[i-1]+s[i];
        }
        CLR(ans);
        f(0, n-1);
        //printf("%d\n", t);
        REP_D(i, 0, output)
        {
            if(ans[i]!=0)
            {
                outPut(ans[i]);
                putchar('\n');
            }
            else
            {
                printf("0\n");
            }
            //printf("%I64d\n", ans[i]);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值