[COCI2009-2010#1] ALADIN

题目描述很简单,就是要求我们计算 ∑ i = 1 n i × A   m o d   B \sum\limits_{i=1}^ni×A\ {\rm mod}\ B i=1ni×A mod B,但是仔细思考会发现 ∑ i = 1 n i × A   m o d   B ≠ ( ∑ i = 1 n i × A )   m o d   B \sum\limits_{i=1}^ni×A\ {\rm mod}\ B≠\left(\sum\limits_{i=1}^ni×A\right)\ {\rm mod}\ B i=1ni×A mod B=(i=1ni×A) mod B,但是根据取模的实质可以知道 ∑ i = 1 n i × A   m o d   B = ∑ i = 1 n i × A − ∑ i = 1 n ⌊ i × A B ⌋ × B \sum\limits_{i=1}^ni×A\ {\rm mod}\ B=\sum\limits_{i=1}^ni×A-\sum\limits_{i=1}^n\left\lfloor\frac{i×A}{B}\right\rfloor×B i=1ni×A mod B=i=1ni×Ai=1nBi×A×B,现在的问题就是如何求出 ∑ i = 1 n ⌊ i × A B ⌋ \sum\limits_{i=1}^n\left\lfloor\frac{i×A}{B}\right\rfloor i=1nBi×A,下面将给出应该能看懂的推导。

本题难在求公差为1的等差数列乘一个数再除以一个数向下取整的和,写成公式:

∑ i = 1 n ⌊ i × q p ⌋ \sum_{i=1}^n\left\lfloor\frac{i×q}{p}\right\rfloor i=1npi×q

直接变换公式好像没什么用,那么我们就尝试将其转换到笛卡尔坐标系中来看(不要问怎么想到的,这是别人给我讲的,应该是一个技巧吧),那么对于一个点 ( i , i × q p ) (i,\frac{i×q}{p}) (i,pi×q) ⌊ i × q p ⌋ \left\lfloor\frac{i×q}{p}\right\rfloor pi×q的值就转换成了点 ( i , 0 ) (i,0) (i,0)到点 ( i , i × q p ) (i,\frac{i×q}{p}) (i,pi×q)之间有多少个整点。那么对于给定的 n , q , p n,q,p n,q,p,答案就变成了求出由直线 y = q p x , x = n , y = 0 y=\frac{q}{p}x,x=n,y=0 y=pqx,x=n,y=0三条直线所围成的一个直角三角形中有多少个整点( y = q p x , x = n y=\frac{q}{p}x,x=n y=pqx,x=n上的整点也要计入答案, y = 0 y=0 y=0上的整点不计入答案),例如当 n = 4 , q = 3 , p = 4 n=4,q=3,p=4 n=4,q=3,p=4的时候,如图所示:

在这里插入图片描述

观察图片就能知道答案为 6 6 6,那么现在的问题就是如何求出整点个数。以上面为例,看起来好像直接求出 3 × 4 3×4 3×4的网格里有多少个整点,然后除以 2 2 2再加上对角线上的点就能得到答案,但是事实并非如此,因为三角形的高是一个小数,用上面方法求解时候只用了整数部分,而且点的分布并不是中心对称的,以 n = 5 , q = 4 , p = 7 n=5,q=4,p=7 n=5,q=4,p=7为例:

在这里插入图片描述

其中实线为正确的分界,可以发现两边的点分布不均匀,虚线为错误方法认为的分界,与正确分界的答案不一样,所以上面的方法并不正确。

那么下面就要讲一个比较神奇的方法,我也不知道他们是怎么想到的,总之就当成结论记下就好。

1. q ≥ p q≥p qp

以直线 y = q p x , y = q   m o d   p p x , x = n y=\frac{q}{p}x,y=\frac{q\ {\rm mod}\ p}{p}x,x=n y=pqx,y=pq mod px,x=n所围成的三角形里的整点个数为一个等差数列,个数为 n × ( n + 1 ) 2 ⌊ q p ⌋ \frac{n×(n+1)}{2}\left\lfloor\frac{q}{p}\right\rfloor 2n×(n+1)pq,证明:

我们将 q p \frac{q}{p} pq转换成带分数的形式 v k p v\frac{k}{p} vpk,可以知道 k = q   m o d   p , v = ⌊ q p ⌋ k=q\ {\rm mod}\ p,v=\left\lfloor\frac{q}{p}\right\rfloor k=q mod p,v=pq,对于上面所说的三角形里整点的个数就是以下公式:

∑ i = 1 n ⌊ i × q p ⌋ − ⌊ i × k p ⌋ = ∑ i = 1 n ⌊ i × v + i × k p ⌋ − ⌊ i × k p ⌋ \sum\limits_{i=1}^n\left\lfloor i×\frac{q}{p}\right\rfloor-\left\lfloor i×\frac{k}{p}\right\rfloor=\sum\limits_{i=1}^n\left\lfloor i×v+i×\frac{k}{p}\right\rfloor-\left\lfloor i×\frac{k}{p}\right\rfloor i=1ni×pqi×pk=i=1ni×v+i×pki×pk

对于第一个向下取整,因为 i × v i×v i×v必定为整数,所以向下取整只是对 i × k p i×\frac{k}{p} i×pk产生影响,所以公式就可以变为下面这种样子:

∑ i = 1 n i × v + ⌊ i × k p ⌋ − ⌊ i × k p ⌋ = ∑ i = 1 n i × v \sum\limits_{i=1}^ni×v+\left\lfloor i×\frac{k}{p}\right\rfloor-\left\lfloor i×\frac{k}{p}\right\rfloor=\sum\limits_{i=1}^ni×v i=1ni×v+i×pki×pk=i=1ni×v

所以对于新三角形中整点个数为: n × ( n + 1 ) 2 v = n × ( n + 1 ) 2 ⌊ q p ⌋ \frac{n×(n+1)}{2}v=\frac{n×(n+1)}{2}\left\lfloor\frac{q}{p}\right\rfloor 2n×(n+1)v=2n×(n+1)pq

下面给一个以 n = 5 , q = 11 , p = 5 n=5,q=11,p=5 n=5,q=11,p=5为例的图片,其中虚线为直线 y = q   m o d   p p x y=\frac{q\ {\rm mod}\ p}{p}x y=pq mod px,蓝色点为减掉的点:

在这里插入图片描述

那么我们现在就只需要求用直线 y = q   m o d   p p x , y = 0 , x = n y=\frac{q\ {\rm mod}\ p}{p}x,y=0,x=n y=pq mod px,y=0,x=n所围成的三角形里的整点个数,这正好就是我们原本需要求的三角形的三边形式,也就是说我们可以考虑递归求解,直到 q   m o d   p = 0 q\ {\rm mod}\ p=0 q mod p=0,就可以算出所有的整点,但是只有 q ≥ p q≥p qp时能进行上面的操作,操作一次后 q < p q<p q<p,然后上面的操作也不能用了,这时候就需要用到下面所讨论的方法。

2. q < p q<p q<p

n = 5 , q = 4 , p = 7 n=5,q=4,p=7 n=5,q=4,p=7为例(注意:我们需要计算的整点是在右下角的三角形里,但是为了方便讲解,这里将其补成了一个矩形):

在这里插入图片描述

虽然现在看起来不能用上面的方法做,但是如果我们将图像进行翻折,好像就可以继续使用上面的方法了。

在这里插入图片描述

不过观察图片可以发现翻折后不仅需要计算的点的位置发生改变,而且下底还不是整数,使得上面的方法依然不能用,这时候就需要用到补集转化的思想,也就是求出所有点再减去不合法的点,不过因为斜边上的点也是在答案中的,所以还要加上被减掉的斜边上的点。

在这里插入图片描述

观察图片可以看到不合法的点就是虚线所围成的三角形里的点,而且这个三角形有一个好处就是下底是整数,根据翻折之前的图片来看,它的底边长度为 ⌊ n × q p ⌋ \left\lfloor\frac{n×q}{p}\right\rfloor pn×q,那么现在就变成了求出由直线 y = p q x , x = ⌊ n × q p ⌋ , y = 0 y=\frac{p}{q}x,x=\left\lfloor\frac{n×q}{p}\right\rfloor,y=0 y=qpx,x=pn×q,y=0(注意第一个直线解析式因为图像的翻折使得 p , q p,q p,q分子分母的位置发生了交换)三条直线所围成的一个直角三角形中有多少个整点,这样就可以递归算下去了。

那么斜边上的点怎么算?根据斜边的直线解析式 y = q p x y=\frac{q}{p}x y=pqx可以知道当 x x x p p p的倍数的时候才是整点,但是 q , p q,p q,p可能可以约分,所以更准确的来说,斜边上的整点个数为: ⌊ n p g c d ( p , q ) ⌋ = ⌊ n × g c d ( p , q ) p ⌋ \left\lfloor\frac{n}{\frac{p}{gcd(p,q)}}\right\rfloor=\left\lfloor\frac{n×gcd(p,q)}{p}\right\rfloor gcd(p,q)pn=pn×gcd(p,q),那么现在所有的问题都解决了。

3.时间复杂度

g e t v a l ( a , b , c ) getval(a,b,c) getval(a,b,c)表示求当 n = a , q = b , p = c n=a,q=b,p=c n=a,q=b,p=c时三角形的整点个数,那么上面的讲解可以大概分成下面几个判断:

1.若 b ≥ c b≥c bc,答案为 a × ( a + 1 ) 2 ⌊ b c ⌋ + g e t v a l ( a , b   m o d   c , c ) \frac{a×(a+1)}{2}\left\lfloor\frac{b}{c}\right\rfloor+getval(a,b\ {\rm mod}\ c,c) 2a×(a+1)cb+getval(a,b mod c,c)

2.若 b < c b<c b<c,答案为 a × ⌊ a × b c ⌋ − g e t v a l ( ⌊ a × b c ⌋ , c , b ) + ⌊ a × g c d ( b , c ) c ⌋ a× \left\lfloor\frac{a×b}{c}\right\rfloor-getval(\left\lfloor\frac{a×b}{c}\right\rfloor,c,b)+\left\lfloor\frac{a×gcd(b,c)}{c}\right\rfloor a×ca×bgetval(ca×b,c,b)+ca×gcd(b,c)

3.若 b = 0 b=0 b=0,答案为 0 0 0

观察 b , c b,c b,c的变化,这不就是类似于求 g c d gcd gcd的过程吗,因为中途也需要计算 g c d gcd gcd来求斜边上的点,所以这种方法的时间复杂度最坏是 ( l o g   m a x ( q , p ) ) 2 ({\rm log}\ max(q,p))^2 (log max(q,p))2的。

可以先练一练:整点与质数

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const LL mod=1e9+7;
int T;LL n,m;
LL power(LL v,LL p)
{
    LL ans=1;
    for(;p;p>>=1,v=v*v%mod)
     if(p&1)ans=ans*v%mod;
    return ans;
}
LL gcd(LL a,LL b)
{
    if(!b)return a;
    return gcd(b,a%b);
}
LL getval(LL n,LL a,LL b)
{
    if(a%b==0)return (n*(n+1)/2)*(a/b);
    if(a<b)return n*(n*a/b)-getval(n*a/b,b,a)+n*gcd(a,b)/b;
    return (n*(n+1)/2)*(a/b)+getval(n,a%b,b);
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld%lld",&n,&m);
        printf("%lld\n",(getval(n/2,m,n)%mod+(n-1)*(m-1)%mod*power(2,mod-2)%mod)%mod);
    }
    return 0;
}

[COCI2009-2010#1] ALADIN

现在这道题就变得很简单了,只需要离散化一下,然后用线段树来维护,核心的东西在上面都已经证明了,还有注意一下代码里的 g e t s u m getsum getsum函数才是上面提到的 g e t v a l getval getval函数。时间复杂度最坏 O ( n ( l o g n ) 3 ) O(n({\rm log}n)^3) O(n(logn)3)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=50010;
int n,m,tot,uni[N*2];
struct ins
{
    int pd,l,r,a,b;
}ask[N];
struct segment
{
    int l;
    long long sum,a,b;
}seg[N*8];
long long gcd(long long a,long long b)
{
    if(!b)return a;
    return gcd(b,a%b);
}
long long getsum(long long v,long long a,long long b)
{
    if(a%b==0)return (v*(v+1)/2)*(a/b);
    if(a<b)return (a*v)/b*v-getsum((a*v)/b,b,a)+v*gcd(a,b)/b;
    return v*(v+1)/2*(a/b)+getsum(v,a%b,b);
}
long long getval(long long v,long long a,long long b)
{
    return v*(v+1)/2*a-getsum(v,a,b)*b;
}
long long slove(int o,int l,int r,int l1,int a,int b)
{
    seg[o].a=a;seg[o].b=b;seg[o].l=l1;
    l=uni[l]-1;r=uni[r+1]-1;
    seg[o].sum=getval(r-uni[l1]+1,a,b);
    seg[o].sum-=getval(l-uni[l1]+1,a,b);
}
void pushdown(int o,int mid,int l,int r)
{
    if(!seg[o].a)return;
    slove(o*2,l,mid,seg[o].l,seg[o].a,seg[o].b);
    slove(o*2+1,mid+1,r,seg[o].l,seg[o].a,seg[o].b);
    seg[o].a=seg[o].b=seg[o].l=0;
}
void update(int o,int l,int r,int l1,int l2,long long a,long long b)
{
    if(l1<=l&&r<=l2)
    {
        slove(o,l,r,l1,a,b);
        return;
    }
    int mid=l+r>>1;pushdown(o,mid,l,r);
    if(l1<=mid)update(o*2,l,mid,l1,l2,a,b);
    if(mid<l2)update(o*2+1,mid+1,r,l1,l2,a,b);
    seg[o].sum=seg[o*2].sum+seg[o*2+1].sum;
}
long long find(int o,int l,int r,int l1,int l2)
{
    if(l1<=l&&r<=l2)return seg[o].sum;
    int mid=l+r>>1;
    long long ans=0;
    pushdown(o,mid,l,r);
    if(l1<=mid)ans+=find(o*2,l,mid,l1,l2);
    if(mid<l2)ans+=find(o*2+1,mid+1,r,l1,l2);
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&ask[i].pd);
        if(ask[i].pd==1)scanf("%d%d%d%d",&ask[i].l,&ask[i].r,&ask[i].a,&ask[i].b);
        else scanf("%d%d",&ask[i].l,&ask[i].r);
        uni[++tot]=ask[i].l;
        uni[++tot]=ask[i].r+1;
    }
    sort(uni+1,uni+tot+1);
    tot=unique(uni+1,uni+tot+1)-uni-1;
    uni[++tot]=n+1;
    for(int i=1;i<=m;i++)
    {
        int l1=lower_bound(uni+1,uni+tot+1,ask[i].l)-uni;
        int l2=upper_bound(uni+1,uni+tot+1,ask[i].r)-uni-1;
        if(ask[i].pd==1)
         update(1,1,tot,l1,l2,ask[i].a,ask[i].b);
        else
         printf("%lld\n",find(1,1,tot,l1,l2));
    }
    return 0;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值