原题重现,最为致命
【本题的空间限制:32MB】
题目描述:
liu_runda决定提高一下知识水平,于是他去请教郭神.郭神随手就给了liu_runda一道神题,liu_runda并不会做,于是把这个题扔到联考里给高二的做.
郭神有n条位于第一象限内的线段,给出每条线段与x轴和y轴交点的坐标,显然这样就可以唯一确定每一条线段.
n条线段和y轴交点的纵坐标分别为1,2,3,4…n.我们记和y轴交点纵坐标为i的线段和x轴交点的横坐标为x[i]+1,x[i]按这样的方式生成:
x[1]由输入给出.
x[i]=(x[i-1]+a) % mod,2<=i<=n.
即:如果x[3]=4,则与y轴交点纵坐标为3的抛物线,和x轴交点的横坐标为4+1=5.
我们保证给出的n,x[1],a,mod使得所有的x[i]互不相同.
对于第一象限内的所有点(点的横纵坐标可以是任意实数),如果一个点被x条线段经过,它的鬼畜值就是x*(x-1)/2.
求第一象限内的所有点的鬼畜值之和.
输入格式
一行4个整数n,x[1],a,mod
输出格式
1行一个整数表示鬼畜值之和.
【数据范围】
第1,2个测试点,n<=100.
第3,4个测试点,n<=10^5.
第5,6个测试点的数据,a<=10.
第7,8个测试点,x[1]=a.
第9,10个测试点,无特殊限制.
对于全部数据,1<=n<=1e7,1<=a<=1e5,1<=mod<=1e8,a,mod互质,n<mod,给出的n,x[1],a,mod使得所有的x[i]互不相同.
请选手注意,1e7个int类型的变量将占用大约40mb的内存,导致内存超限,本题得0分.
【分析】:
1.先来转化问题,一个点如果被n个点经过,贡献为 ( n − 1 ) ∗ ( n ) 2 \frac{(n-1)*(n)}{2} 2(n−1)∗(n),然后你就会发现这东西是 C n 2 C_{n}^2 Cn2, C n 2 C_{n}^2 Cn2是什么?不就是n个里面选2个的方案数吗,那么每选两个就会产生1的贡献。----转化问题:两条相交的线段就会产生1的贡献,由于y是递增的,转变为求x数组的逆序对。
限于空间的限制,我们直接求逆序对可以获得40分的高分,考虑如何根据本题的特殊性质 x [ i ] = ( x [ i − 1 ] + a ) % m o d x[i]=(x[i-1]+a) \% mod x[i]=(x[i−1]+a)%mod 来进行优化。
2.不难发现所有的 x [ ] x[ ] x[]进行分类以后会划分成几个等差数列的形式.
因为若 x [ i − 1 ] + a > = m o d x[i-1]+a>=mod x[i−1]+a>=mod,那么 x [ i ] = x [ i − 1 ] + a − m o d x[i]=x[i-1]+a-mod x[i]=x[i−1]+a−mod,那么 x [ i ] x[i] x[i]就是一个新的等差数列的首项,并且我们很容易证明 x [ i ] < a x[i]<a x[i]<a (因为 x [ i − 1 ] < m o d x[i-1]<mod x[i−1]<mod,所以 x [ i − 1 ] − m o d < 0 x[i-1]-mod<0 x[i−1]−mod<0)。然后对于 x [ i − 1 ] + a < m o d x[i-1]+a<mod x[i−1]+a<mod递推来的 x [ i ] x[i] x[i]显然也是一个等差数列的项,公差为 a a a。
3.
x
[
1
]
=
a
x[1]=a
x[1]=a的数据告诉我们,
x
[
i
]
=
i
∗
a
%
m
o
d
x[i]=i*a\%mod
x[i]=i∗a%mod
假设
x
[
i
]
=
x
[
i
−
1
]
+
a
x[i]=x[i-1]+a
x[i]=x[i−1]+a(也就是
x
[
i
−
1
]
+
a
<
m
o
d
x[i-1]+a<mod
x[i−1]+a<mod),且
x
[
i
−
1
]
x[i-1]
x[i−1]和前面所有数字形成了m个逆序对,同时,除去
x
[
i
−
1
]
x[i-1]
x[i−1]和
x
[
i
]
x[i]
x[i]所在的等差数列,x[i]前面的所有数字可以分成
k
k
k段等差序列,那么
x
[
i
]
x[i]
x[i]将和前面所有数字组成
m
−
k
m-k
m−k个逆序对.
原因在于:每段等差序列中必然有一个数字和 x [ i − 1 ] x[i-1] x[i−1]能组成逆序对,但不能和 x [ i ] x[i] x[i]组成逆序对.那么每段等差数列的贡献都会减1,因为每一个等差数列虽然不同,但是公差相同,那么相当于是一个错位。即前面一个等差数列中第一个大于 x [ i − 1 ] x[i-1] x[i−1]一定会小于 x [ i ] x[i] x[i]
因此我们可以
O
(
1
)
O(1)
O(1)从
x
[
i
−
1
]
x[i-1]
x[i−1]的贡献得到x[i]的贡献.
如果
x
[
i
]
<
a
x[i]<a
x[i]<a,不存在对应的
x
[
i
−
1
]
x[i-1]
x[i−1](因为一个等差数列的首相),我们需要直接计算它的贡献,前面有
i
−
1
i-1
i−1个数字,我们数出有多少个数字不产生贡献(即小于
x
[
i
]
x[i]
x[i]的数字个数),即可求出有多少个数字形成了逆序对.用树状数组维护小于
a
a
a的所有数值,可以在
O
(
l
o
g
a
)
O(loga)
O(loga)的时间内完成一次这样的计算.小于
a
a
a的数字至多有
a
a
a个,所以这一部分的时间复杂度为O(
a
l
o
g
a
aloga
aloga),空间复杂度为O(
a
a
a)
4.然而 x [ 1 ] x[1] x[1]不一定等于 a a a,那么第一段等差数列就不一定是完整的,只是取了一个后缀,那么在计算 x [ i ] x[i] x[i]的贡献时,第一段的首项即 x [ 1 ] x[1] x[1]如果大于 x [ i − 1 ] x[i-1] x[i−1]也有可能会大于 x [ i ] x[i] x[i],不符合我们上面说的,对于这种情况我们需要进行特判+1。
Code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5;
int n,a,x,mod,k=0;//k表示当前数前面(不包含该数所在的等差数列)有多少个等差数列
int c[N+100];
ll ans=0,sum=0;
int t=0;
void add(int x)
{
for(;x<=N;x+=x&-x)
c[x]+=1;
}
ll ask(int x)
{
ll ans=0;
for(;x;x-=x&-x)
ans+=c[x];
return ans;
}
int main()
{
freopen("fly.in","r",stdin);
freopen("fly.out","w",stdout);
scanf("%d%d%d%d",&n,&x,&a,&mod);
int x1=x;
for(int i=1;i<=n;i++)
{
if(x>=a)
{
ans-=k;//由x[i-1]的贡献转移到x[i]
if(x<x1) ans++;//对于第一个不完整的等差数列我们需要特判,补回多减的情况
sum+=ans;
}
else //x[i]<a---x[i]为一个等差数列的首项
{
ans=(i-1)-ask(x+1);//由于x可能为0,但是树状数组下标不可为0,整体加1即可
sum+=ans;
add(x+1);
}
/*ans表示当前第i个数和前面数形成的逆序对数*/
x+=a;
if(x>=mod) x-=mod,k++;
}
printf("%lld\n",sum);
return 0;
}