liu_runda NOIP 联考 DAY1[飞]题解(小学生都看得懂!)

原题重现,最为致命

【本题的空间限制: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(n1)(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[i1]+a)%mod 来进行优化。

2.不难发现所有的 x [ ] x[ ] x[]进行分类以后会划分成几个等差数列的形式.

因为若 x [ i − 1 ] + a > = m o d x[i-1]+a>=mod x[i1]+a>=mod,那么 x [ i ] = x [ i − 1 ] + a − m o d x[i]=x[i-1]+a-mod x[i]=x[i1]+amod,那么 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[i1]<mod,所以 x [ i − 1 ] − m o d < 0 x[i-1]-mod<0 x[i1]mod<0)。然后对于 x [ i − 1 ] + a < m o d x[i-1]+a<mod x[i1]+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]=ia%mod
假设 x [ i ] = x [ i − 1 ] + a x[i]=x[i-1]+a x[i]=x[i1]+a(也就是 x [ i − 1 ] + a < m o d x[i-1]+a<mod x[i1]+a<mod),且 x [ i − 1 ] x[i-1] x[i1]和前面所有数字形成了m个逆序对,同时,除去 x [ i − 1 ] x[i-1] x[i1] x [ i ] x[i] x[i]所在的等差数列,x[i]前面的所有数字可以分成 k k k段等差序列,那么 x [ i ] x[i] x[i]将和前面所有数字组成 m − k m-k mk个逆序对.

原因在于:每段等差序列中必然有一个数字和 x [ i − 1 ] x[i-1] x[i1]能组成逆序对,但不能和 x [ i ] x[i] x[i]组成逆序对.那么每段等差数列的贡献都会减1,因为每一个等差数列虽然不同,但是公差相同,那么相当于是一个错位。即前面一个等差数列中第一个大于 x [ i − 1 ] x[i-1] x[i1]一定会小于 x [ i ] x[i] x[i]

因此我们可以 O ( 1 ) O(1) O(1) x [ i − 1 ] x[i-1] x[i1]的贡献得到x[i]的贡献.
如果 x [ i ] < a x[i]<a x[i]<a,不存在对应的 x [ i − 1 ] x[i-1] x[i1](因为一个等差数列的首相),我们需要直接计算它的贡献,前面有 i − 1 i-1 i1个数字,我们数出有多少个数字不产生贡献(即小于 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[i1]也有可能会大于 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;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值