HDU 5751 BestCoder Round #84 Eades(线段树+FFT)

23 篇文章 0 订阅
13 篇文章 0 订阅

Eades

Time Limit: 12000/6000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 140    Accepted Submission(s): 71

Problem Description
Peter has a number sequence a1,a2,...,an . Let g(l,r) be the maximum value of the subsequence al,al+1,...,ar and f(l,r)=i=lr[ai=g(l,r)] . Note that [condition]=1 if condition is true, or 0 otherwise.

Peter wants to know the number of integer pairs l and r (lr) such that f(l,r)=k , for every integer k{1,2,...,n} .
Input
There are multiple test cases. The first line of input contains an integer T indicating the number of test cases. For each test case:

The first contains an integer n (1n60000) -- the length of the sequence. The second line contains n integers a1,a2,...,an (1ain) .

Output
For each test case, output an integer S=k=1nkzk , where zk is the number of integer pairs l and r (lr) that f(l,r)=k and denotes the bitwise exclusive or operation.
Sample Input
  
  
3 3 1 2 3 4 1 1 1 1 6 1 2 2 1 1 2
Sample Output
  
  
12 12 36
Source
Recommend
wange2014   |   We have carefully selected several similar problems for you:  5932 5931 5930 5929 5928 
题意:
给你一个长度为n(60000)的序列a[ ],问你有多少个区间的最大值数的出现数量恰好为k次(k∈[1,n]),让你输出∑(ans[k]^k) 。
题解:  
先说一下样例:

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) 
总区间个数为3+1+8+7+2=21 .
答案为(11)(8)(2)(0)(0)(0) 
=11^1+8^2+0^4+0^5+0^6 (^为异或)
=10 + 10 + 1 + 4 + 5 + 6=36 。

首先我们考虑枚举最大值,可以写一棵线段树来查询最大值。
知道最大值之后,我们可以通过查询出所有最大值点来计算出有多少个区间包含这个最大值。

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

接下来,我们发现,对于ans[k],我们就要找到该区间范围内第i个v,和第i+k-1个v,
然后:假设i左侧有left个可以不延展到v[i-1]的位点
假设i+k-1右侧有right个可以不延展到v[i+k]的位点,
那么,对于ans[k],我们就获得了left*right的答案贡献。
然而,我们不能暴力算,暴力算的复杂度为O(n^2) 。

这里就需要FFT了。
我们求出,每个区间最大值的点,在不延展到前一个区间最大值点的条件下,其可以向左延展多少个数,记做c[]
显然,在我们求出区间内的所有数之后,比如区间内,p[v][left]~p[v][right]都是权值为v的点位
那么:
c[0]=p[v][left]- l + 1,
c[num]=r-p[v][right]+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(n*logn)。 

详细地解释可以看HDU聚聚 ,我是根据他的题解来写这题的。

AC代码:
#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
#include<algorithm>
typedef long long ll;
using namespace std;
#define lson  l , mid , rt << 1  
#define rson  mid + 1 , r , rt << 1 | 1  

const int N = 6e5+10;
const double pi = acos(-1.0);
const int mod=313; 
char s1[N],s2[N];
int len,res[N];

struct Complex
{
    double r,i;
    Complex(double r=0,double i=0):r(r),i(i) {};
    Complex operator+(const Complex &rhs)
    {
        return Complex(r + rhs.r,i + rhs.i);
    }
    Complex operator-(const Complex &rhs)
    {
        return Complex(r - rhs.r,i - rhs.i);
    }
    Complex operator*(const Complex &rhs)
    {
        return Complex(r*rhs.r - i*rhs.i,i*rhs.r + r*rhs.i);
    }
} va[N],vb[N];

//雷德算法--倒位序  
void rader(Complex F[],int len) //len = 2^M,reverse F[i] with  F[j] j为i二进制反转
{
    int j = len >> 1;
    for(int i = 1;i < len - 1;++i)
    {
        if(i < j) swap(F[i],F[j]);  // reverse
        int k = len>>1;
        while(j>=k)
        {
            j -= k;
            k >>= 1;
        }
        if(j < k) j += k;
    }
}
//FFT实现
void FFT(Complex F[],int len,int t)
{
    rader(F,len);
    for(int h=2;h<=len;h<<=1) //分治后计算长度为h的DFT 
    {
        Complex wn(cos(-t*2*pi/h),sin(-t*2*pi/h)); //单位复根e^(2*PI/m)用欧拉公式展开
        for(int j=0;j<len;j+=h)
        {
            Complex E(1,0); //旋转因子
            for(int k=j;k<j+h/2;++k)
            {
                Complex u = F[k];
                Complex v = E*F[k+h/2];
                F[k] = u+v; //蝴蝶合并操作
                F[k+h/2] = u-v;
                E=E*wn; //更新旋转因子
            }
        }
    }
    if(t==-1)   //IDFT
        for(int i=0;i<len;++i)
            F[i].r/=len;
}

//求卷积 
void Conv(Complex a[],Complex b[],int len) 
{
    FFT(a,len,1);
    FFT(b,len,1);
    for(int i=0;i<len;i++) b[i] = a[i]*b[i];
    FFT(b,len,-1);
}

ll ans[N];
int n;
int mx[1<<18];
vector<int> p[N];
int a[N];
Complex x[1<<18],y[1<<18];

void build(int l, int r, int rt)
{
	if(l==r)
	{
		mx[rt]=a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(lson);
	build(rson);
	mx[rt]=max( mx[rt<<1], mx[rt<<1|1] );
}

int getmax(int l, int r, int rt, int L, int R)  
{  
    if (l >= L &&r <= R) return mx[rt];  
    int mid = (l + r) >> 1;  
    int ret = 0;  
    if (L <= mid) ret=max(ret, getmax(lson, L, R));  
    if (R > mid)  ret=max(ret, getmax(rson, L, R));  
    return ret;  
}  

void gao(int l,int r)  
{  
	if (l > r)return;  
    if (l == r)  
    {  
        ans[1]++;  
        return;  
    }  
    int v = getmax(1, n, 1, l, r);  
    
    int left = lower_bound(p[v].begin(), p[v].end(), l) - p[v].begin();  
    int right = upper_bound(p[v].begin(), p[v].end(), r) - p[v].begin() - 1;  
    
    vector<int> pos;  
    
    pos.push_back(l - 1);  
    for (int i = left; i <= right; i++)
		pos.push_back(p[v][i]);  
		
    pos.push_back(r + 1);  
    
    int num = right - left + 2;  
    for (int i = 0; i < num; i++)
    {
    	x[i] =  Complex( 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] = Complex(0,0);  
	}
    Conv(x,y,len);
    
    for (int i = 0; i < num - 1; i++)
	{
		ans[num - 1 - i] += ( y[i].r + 0.5); 
	} 
    for (int i = 0; i < num; i++)  
    {  
        gao(pos[i] + 1, pos[i + 1] - 1);  
    }  
}  
void solve()  
{  
	int t;
    scanf("%d", &t);  
    while(t--)
    {
        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,n,1);  
        gao(1, n);  
        ll ANS = 0;  
        for (int i = 1; i <= n; ++i)
		{
			ANS += ans[i] ^ i;
		} 
		  
        printf("%lld\n", ANS);  
    }
}  

int main()
{
    solve();
    return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值