fly(树状数组)

10.7

算法1:
直接按照题目描述计算鬼畜值.容易发现,虽然第一象限内有无数个点,但只被一条线段经过的点的鬼畜值一定是0,所以我们只考虑至少被两条线段经过的点,这样的点最多有n^2个,去去重就可以按照定义算鬼畜值了.复杂度至少为O(n^2),视后续的处理方式时间复杂度可能更高.
可以得到n<=100时的20分.
算法2:
仔细观察题目中鬼畜值的计算公式,发现实际上是C(x,2),即每一对在这个点相交的线段,对答案贡献为1,且(a,b)和(b,a)的贡献只算1次.因此我们只需要计算C(n,2)对线段中有几对是相交的.如果直接两两判断是否相交,复杂度O(n^2).
依然只能得到n<=100时的20分.
算法3:
考虑线段相交的性质.显然有这样的结论:0 < a < b,0 < c < d,那么在(0,a),(c,0)之间连线段,在(0,b),(d,0)之间连线段,这样的两条线段在第一象限一定没有交点.
我们自然得出:答案就是x[1],x[2]…x[n]这个序列的逆序对个数.
考场上这个结论可以通过大样例进行检验.
采用经典的树状数组或分治法在O(nlogn)的时间内求出逆序对数目即可.
可以得到n<=100000时的40分.
算法4:
可能有的选手对自己的常数非常自信,试图用O(nlogn)的方法直接AC本题.
如果没有特殊的压内存技巧,将因为32mb内存限制下,开不出1e7的int数组得到内存超限的0分.
出题人估计考场上不会出现能够把时间和空间常数都用此算法卡进本题限制的选手.
算法5:
考虑树状数组算法,我们需要求出第i个元素和前面i-1个元素形成的逆序对个数.
整个序列由若干段等差数列组成(不超过a段).而第i个元素前面的i-1个元素也可以分成不超过a段等差数列.在每段等差数列内大于第i个元素的元素个数可以O(1)求出,因此O(a)的时间内即可求出第i个元素和前面i-1个元素组成的逆序对数.时间复杂度O(na).
可以得到a<=10时的20分.结合算法3可以得到60分.
算法6:
x[1]=a的数据告诉我们,x[i]=i*a%mod
假设x[i]=x[i-1]+a(也就是x[i-1]+a < mod),且x[i-1]和前面所有数字形成了m个逆序对,同时,除去x[i-1]和x[i]所在的等差数列,x[i]前面的所有数字可以分成k段等差序列,那么x[i]将和前面所有数字组成m-k个逆序对.
原因在于:每段等差序列中必然有一个数字和x[i-1]能组成逆序对,但不能和x[i]组成逆序对.那么每段等差数列的贡献都会减1.
因此我们可以O(1)从x[i-1]的贡献得到x[i]的贡献.
如果x[i] < a,不存在对应的x[i-1],我们需要直接计算它的贡献.前面有i-1个数字,我们数出有多少个数字不产生贡献(即小于x[i]的数字个数),即可求出有多少个数字形成了逆序对.用树状数组维护小于a的所有数值,可以在O(loga)的时间内完成一次这样的计算.小于a的数字至多有a个,所以这一部分的时间复杂度为O(aloga),空间复杂度为O(a)
总的时间复杂度为O(aloga+n)
可以得到x[1]=a的20分.
算法7:
算法6几乎就是满分做法了.现在x[1]!=a,我们只需针对最开始的一段不完整等差数列加一些特判就可以通过本题.
可以得到100分.
算法8:
实际上存在更加优越的算法,复杂度为O(aloga),不需要带有一个O(n)
考虑算法6,7中我们都把整个序列划分为了O(a)段.实际上同一段中所有元素的贡献是一个等差数列的形式,可以直接求和.细节可能较多.

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define LL long long
#define N 100010
using namespace std;

int n, x, A, mod;
LL ans;
int c[N];

void add( int x ){
    for(; x <= A; x += x&-x ) 
        c[x]++; 
}

int query( int x ){
    int rt = 0;
    for(; x; x -= x&-x ) 
        rt += c[x];
    return rt;
}

int main(){
    freopen("fly.in", "r", stdin);
    freopen("fly.out", "w", stdout);
    scanf("%d%d%d%d", &n, &x, &A, &mod); 
    x++;
    if(x <= A) add( x );
    int now = x, cnt = 0, res = 0;
    for(int i=2; i<=n; i++){
        if(now - 1 + A >= mod){
            cnt++;
            now = (now + A) % mod;
        } 
        else now += A;
        if( !cnt ) continue;
        if( now <= A ){
            res = i - 1 - query( now );
            add( now );
        } 
        else res -= ( now > x ? cnt : cnt - 1 );//第一个循环不全 
        ans += res;
    }
    printf("%I64d", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值