【HDU5751 BestCoder Round 84E】【FFT + 线段树求最值】Eades 最大数出现次数为[1~n]的区间个数

Eades

 
 Accepts: 1
 
 Submissions: 11
 Time Limit: 12000/6000 MS (Java/Others)
 
 Memory Limit: 131072/131072 K (Java/Others)
问题描述
Peter有一个序列a_1, a_2, ..., a_na1,a2,...,an. 定义g(l,r)g(l,r)表示子序列a_{l},a_{l+1},...,a_{r}al,al+1,...,ar的最大值, f(l,r)=\displaystyle\sum_{i=l}^{r}[a_i = g(l,r)]f(l,r)=i=lr[ai=g(l,r)]. 注意[\text{condition}] = 1[condition]=1当且仅当\text{condition}condition是true, 否则[\text{condition}] = 0[condition]=0.

对于每个整数k \in \{1, 2, ..., n\}k{1,2,...,n}, Peter想要知道有多少整数对llrr (l \le r)(lr)满足f(l,r)=kf(l,r)=k.
输入描述
输入包含多组数据, 第一行包含一个整数TT表示测试数据组数. 对于每组数据:

第一行包含一个整数nn (1 \le n \le 60000)(1n60000)表示序列的长度. 第二行包含nn个整数a_1,a_2,...,a_na1,a2,...,an (1 \le a_i \le n)(1ain).
输出描述
对于每组数据, 输出一个整数S = \displaystyle\sum_{k=1}^{n}k \oplus z_kS=k=1nkzk, 其中z_kzk表示满足f(l,r)=kf(l,r)=k的数对llrr的个数, \oplus是异或位运算操作.
输入样例
3
3
1 2 3
4
1 1 1 1
6
1 2 2 1 1 2
输出样例
12
12
36

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; }
const int N = 60010, M = 0, Z = 1e9 + 7, ms63 = 0x3f3f3f3f;
int casenum, casei;
int n;
int a[N];
vector<int>p[N];
int mx[1 << 17];
LL ans[N];
void build(int o, int l, int r)
{
    if (l == r)
    {
        mx[o] = a[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(lson);
    build(rson);
    mx[o] = max(mx[ls], mx[rs]);
}
int getmx(int o, int l, int r, int L, int R)
{
    if (l >= L&&r <= R)return mx[o];
    int mid = (l + r) >> 1;
    int ret = 0;
    if (L <= mid)gmax(ret, getmx(lson, L, R));
    if (R > mid)gmax(ret, getmx(rson, L, R));
    return ret;
}
const double PI = acos(-1.0);
struct Complex
{
    double r, i;
    Complex(double r = 0, double i = 0) : r(r), i(i) {}
    Complex operator + (const Complex& t) const { return Complex(r + t.r, i + t.i); }
    Complex operator - (const Complex& t) const { return Complex(r - t.r, i - t.i); }
    Complex operator * (const Complex& t) const { return Complex(r * t.r - i * t.i, r * t.i + i * t.r); }
}X[1 << 17], Y[1 << 17];
void FFT(Complex y[], int n, int rev)//rev == 1时为DFT,rev == -1时为IDFT
{
    for (int i = 1, j, k, t; i < n; ++i)
    {
        for (j = 0, k = n >> 1, t = i; k; k >>= 1, t >>= 1) j = j << 1 | t & 1;
        if (i < j) swap(y[i], y[j]);
    }
    for (int s = 2, ds = 1; s <= n; ds = s, s <<= 1)
    {
        Complex wn(cos(rev * 2 * PI / s), sin(rev * 2 * PI / s));
        for (int k = 0; k < n; k += s)
        {
            Complex w(1, 0), t;
            for (int i = k; i < k + ds; ++i)
            {
                y[i + ds] = y[i] - (t = w * y[i + ds]);
                y[i] = y[i] + t;
                w = w * wn;
            }
        }
    }
    if (rev == -1) for (int i = 0; i < n; ++i) y[i].r /= n;
}
void solve(int l, int r)
{
    if (l > r)return;
    if (l == r)
    {
        ++ans[1];
        return;
    }
    int v = getmx(1, 1, n, l, r);
    int lft = lower_bound(p[v].begin(), p[v].end(), l) - p[v].begin();
    int rgt = upper_bound(p[v].begin(), p[v].end(), r) - p[v].begin() - 1;
    vector<int>pos;
    pos.push_back(l - 1);
    for (int i = lft; i <= rgt; ++i)pos.push_back(p[v][i]);
    pos.push_back(r + 1);
    int num = rgt - lft + 2;
    for (int i = 0; i < num; ++i)X[i] = { (double)pos[i + 1] - pos[i], 0 };
    for (int i = 0; i < num; ++i)Y[i] = X[num - 1 - i];
    int len = 1; while (len < num + num)len <<= 1;
    for (int i = num; i < len; ++i)X[i] = Y[i] = { 0,0 };
    FFT(X, len, 1);
    FFT(Y, len, 1);
    for (int i = 0; i < len; ++i)Y[i] = X[i] * Y[i];
    FFT(Y, len, -1);
    for (int i = 0; i < num - 1; ++i)ans[num - 1 - i] += (Y[i].r + 0.5);
    for (int i = 0; i < num; ++i)
    {
        solve(pos[i] + 1, pos[i + 1] - 1);
    }
}
int main()
{
    scanf("%d", &casenum);
    for (casei = 1; casei <= casenum; ++casei)
    {
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i)
        {
            p[i].clear();
            ans[i] = 0;
        }
        for (int i = 1; i <= n; ++i)
        {
            scanf("%d", &a[i]);
            p[a[i]].push_back(i);
        }
        build(1, 1, n);
        solve(1, n);
        LL ANS = 0;
        for (int i = 1; i <= n; ++i)ANS += ans[i] ^ i;
        printf("%lld\n", ANS);
    }
    return 0;
}
/*
【trick&&吐槽】
1,注意尽量不要让全局变量数组设计迭代,会重复共享而出错的!
2,注意FFT的len要从1开始倍增,这样才保证其为2的幂次

【题意】
有一个长度为n(60000)的序列a[]。
我们想要知道——有多少个区间最大值数的出现数量恰好为k次(k∈[1,n])

输出:∑(ans[k]^k) k ∈[1,n]

【类型】
线段树 + fft

【分析】
这道题,我们首先思考枚举最大值(这个思路很常见,因为一共只有n个最大值,同时,我们在知道最大值的条件下,更容易算出其对答案的贡献)

我们如何求出最大值呢?
可以写一棵线段树实现。
我们知道最大值之后,如何算出有多少个区间包含这个最大值呢?
我们可以通过查询出所有最大值点来实现。

具体而言,就是,我们把权值为x的所有数,都push_back入p[x]的vector中,存一下权值为x的数的所有位置。
然后进入一个solve(l, r)的递归函数
所谓solve(l, r),就是——
在区间边界不能<l 且 不能 >r(即区间左右界被局限在[l,r]范围内,否则就会打破最大值限制)的条件下,
我们思考该区间内部的最大值,对答案的贡献。
用线段树可以求出该区间的最大值,设为v
然后通过二分查找,查询出在[l,r]范围内,权值为v的所有数。

接下来,我们发现,对于ans[k],我们就要找到该区间范围内第i个v,和第i+k-1个v,然后——
假设i左侧有lft个可以不延展到v[i-1]的位点
假设i+k-1右侧有rgt个可以不延展到v[i+k]的位点,
那么,对于ans[k],我们就获得了lft*rgt的答案贡献。
然而,我们不能暴力算,暴力算的复杂度为O(n^2)
我们需要借助fft的力量。
我们求出,每个区间最大值的点,在不延展到前一个区间最大值点的条件下,其可以向左延展多少个数,记做c[]
显然,在我们求出区间内的所有数之后,比如区间内,p[v][lft]~p[v][rgt]都是权值为v的点位
那么c[0]=p[v][lft]-l+1,
c[num]=r-p[v][rgt]+1,
c[i]=p[v][x]-p[v][x-1]

那么,对于ans[k],我们所获得的贡献是∑c[i]*c[i+k]。
而这个所对应的,就相当于是卷积FFT。
c[0] c[1] c[2] c[3] c[4]
c[0] c[1] c[2] c[3] c[4]
它们做FFT之后,我们可以得到——
(c[0] * c[0])
(c[0] * c[1] + c[1] * c[0])
(c[0] * c[2] + c[1] * c[1] + c[2] *c[1])
(c[0] * c[3] + c[1] * c[2] + c[2] * c[1] + c[3] * c[0])
(c[0] * c[4] + c[1] * c[3] + c[2] * c[2] + c[3] * c[1] + c[4] * c[0])
(c[1] * c[4] + c[2] * c[3] + c[3] * c[2] + c[4] * c[1])
(c[2] * c[4] + c[3] * c[3] + c[4] * c[2])
(c[3] * c[4] + c[4] * c[3])
(c[4] * c[4])


做一下对称反转
c[0] c[1] c[2] c[3] c[4]
c[4] c[3] c[2] c[1] c[0]
它们做FFT之后,我们可以得到——
(c[0] * c[4]) ->ans[4]
(c[0] * c[3] + c[1] * c[4]) ->ans[3]
(c[0] * c[2] + c[1] * c[3] + c[2] *c[4]) ->ans[2]
(c[0] * c[1] + c[1] * c[2] + c[2] * c[3] + c[3] * c[4]) ->ans[1]
(c[0] * c[0] + c[1] * c[1] + c[2] * c[2] + c[3] * c[3] + c[4] * c[4])
(c[1] * c[0] + c[2] * c[1] + c[3] * c[2] + c[4] * c[3]) ->ans[1]
(c[2] * c[0] + c[3] * c[1] + c[4] * c[2]) ->ans[2]
(c[3] * c[0] + c[4] * c[1]) ->ans[3]
(c[4] * c[0]) ->ans[4]

也就生成了对ans[4],ans[3],ans[2],ans[1]...的贡献。(我们只要拿一半统计、计数即可)

【时间复杂度&&优化】
O(nlogn)
复杂度如何算呢?
每个点最多参与一次fft,所以复杂度大概为O(nlogn)

【数据】
样例解释——
6
1 2 2 1 1 2
最大值为1时:
最大值个数为1、2的区间个数分别为:3 1

最大值为2时:
最大值个数为1、2、3的区间个数分别为:(1+1+3+1+1+1)(3+3+1)(2)=(8)(7)(2)

总区间个数为21
答案为(11)(8)(2)(0)(0)(0)
=10 + 10 + 1 + 4 + 5 + 6=36


*/


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值