bzoj3565

题意:
超能粒子炮由垂直方向从上到下共 n 个超能粒子发射管构成,编号 1~n。所有的发射管都会在开火的一瞬间同时发射出强大的超能粒子流。
为了彻底摧毁再生能力极强的邪恶宇宙怪物 CCM,地球联合军将 CCM 从上到下分为 m 个区域, 编号 1~m,分别进行打击。其中超能粒子炮的第 i 号发射管将会对准 f(i)号区域发射。f(i) 的公式如下:
f(i) = (a * i + b) mod m + 1
其中 a, b 都是给定的常数。
你需要知道有多少对粒子流 i, j,满足 i< j 且 f(i) > f(j)。

#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<iostream>
#define N 1100
#define LL long long
using namespace std;
int n,m,a,b,st[N],num;
LL ans,cnt[N];
bool bo;
int qmod(LL a,int b,LL mmod)
{
    LL t=1;
    while(b)
    {
        if(b%2) t=t*a%mmod;
        b/=2;
        a=a*a%mmod;
    }
    return t;
}
void solve(int x,int y)
{
    if(cnt[x]<=cnt[y]) ans+=(1ll+cnt[x])*cnt[x]/2;
    else ans+=(1ll+cnt[y])*cnt[y]/2+(cnt[x]-cnt[y])*cnt[y];
}
void cal(int x,int y)
{
    int xst=st[x],xc=cnt[x],yst=st[y],yc=cnt[y],k;
    if(st[x]+(cnt[x]-1)*a<st[y]) return;
    if(st[x]>st[y]+(cnt[y]-1)*a) {ans+=cnt[x]*cnt[y];return;}
    if(st[x]<st[y])
    {
        k=(st[y]-st[x])/a+1;
        st[x]=st[x]+k*a;
        cnt[x]-=k;
    }
    else
    {
        k=(st[x]-st[y])/a;
        ans+=cnt[x]*k;
        st[y]=st[y]+k*a;
        cnt[y]-=k;
    }
    solve(x,y);
    st[x]=xst;cnt[x]=xc;
    st[y]=yst;cnt[y]=yc;
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&a,&b);
    int ny=qmod(a,m-2,m);
    if(a>1000)
    {
        bo=1;
        a=ny;
        b=m-(LL)ny*b%m;
    }
    int nex,lim;
    if(bo==0) lim=n;
    else lim=m;
    for(int i=1;i<=lim;i=nex+1)
    {
        int p,k;
        if(bo) p=((LL)(i-1)*a+b)%(LL)m;
        else p=((LL)i*a+b)%(LL)m+1;
        if(bo==0)
        {
            k=min((m-p)/a+1,lim-i+1);
            nex=i+k-1;
            num++;
            st[num]=p;
            cnt[num]=k;
        }
        else
        {
            if(p<=n)
            {
                k=min((n-p)/a+1,lim-i+1);
                num++;
                st[num]=p;
                cnt[num]=k;
            }
            k=min((m-p)/a+1,lim-i+1);
            nex=i+k-1;
        }
    }
    for(int i=1;i<=num;i++)
        for(int j=i+1;j<=num;j++)
            cal(i,j);
    printf("%lld\n",ans);
    return 0;
}

题解:
膜了claris题解。。之前想复杂了T_T
考虑a特别小,那么i大概要增加 ma 次,f(i)才会有一次有效的取模
也就是说f(i)是 O(a) 段等差数列,并且每个等差数列公差都是a,首项各不相同
同一个等差数列中是不会有逆序对的,两两之间逆序对可以O(1)算
O(a2) 就解决了
考虑a’<=1000,有个很厉害的脑洞。。
f(i)=(ai+b)%m+1
((f(i)1)aba)%m=i
这代表什么呢?
之前我们把i做下标,拿出了 O(a) 段等差数列
类似地把(f(i)-1)做下标,就能拿出 O(a) 段等差数列
于是 O(a2) 解决

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页