2019.11.11 模擬賽 T2 乘積求和

昨天 ych 的膜你賽,這道題我 O ( n4 ) 暴力拿了 60 pts。 node

這道題的作法還挺妙的,我搞了將近一天呢qwqios

題解

60 pts 

根據題目給出的式子,四層 for 循環暴力枚舉統計答案便可;數組

#include<iostream>
#include<cstdio>
using namespace std;
int read()
{
    char ch=getchar();
    int a=0,x=1;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') x=-x;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        a=(a<<3)+(a<<1)+(ch-'0');
        ch=getchar();
    }
    return a*x;
}
const long long mod=1e12+7;
int n;
long long ans,a[100000];
int main()
{
    freopen("multiplication.in","r",stdin);
    freopen("multiplication.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int l=1;l<=n;l++)
        for(int r=l;r<=n;r++)
            for(int i=l;i<=r;i++)
                for(int j=i+1;j<=r;j++)
                    if(a[i]>a[j])
                        ans=(ans+a[i]*a[j]%mod)%mod;
    printf("%lld\n",ans);
    return 0;
}

 

80 pts

方法一:預處理 + O ( n2 )數據結構

咱們能夠翻譯一下題目中給出的式子:優化

就是求全部區間中每一個區間的逆序對乘積spa

那麼咱們能夠提早預處理出每一個區間的答案,再統計答案;翻譯

時間複雜的 O ( n2 ),指望得分 80 pts;code

for(int i=1;i<=n;i++)
    {
        for(int j=i;j<=n;j++)
        {
            f[i][j]=f[i][j-1];
            if(a[j]<a[i]) f[i][j]=(f[i][j]+a[i]*a[j]%mod)%mod;
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=i+1;j<=n;j++)
        {
            ans=(ans+i*f[i][j])%mod;
        }
    }

 

方法二:歸併排序blog

考慮一對逆序對 ( ai , aj ) 會在全部的區間內出現幾回 。排序

由於同時包含 a和 a的最小區間是 [ i , j ],因此左端點小於等於 i,右端點大於等於 j 的全部區間也包含,全部共有 i * ( n - j + 1 );

因此咱們能夠去找出全部的逆序對,而後去計算他們對答案的貢獻;

求逆序對,咱們能夠用歸併排序;

具體思路就是在兩部分合並的過程當中,若是左半部分的某個數 ai 大於右半部分某個數 a,這時候從 ai ~ amid 都會與 aj 產生一組逆序對,咱們統計答案就行了;

#include<iostream>
#include<cstdio>
using namespace std;
long long read()
{
    char ch=getchar();
    long long a=0,x=1;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') x=-x;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        a=(a<<3)+(a<<1)+(ch-'0');
        ch=getchar();
    }
    return a*x;
}
const long long mod=1e12+7;
const int N=5e4;
long long n,ans;
struct node
{
    long long val,id;
}a[N],c[N];
void gb_sort(int l,int r)
{
    if(l==r) return ;
    int mid=(l+r)>>1;
    gb_sort(l,mid);
    gb_sort(mid+1,r);
    int i=l,j=mid+1;
    int k=l-1;
    while(i<=mid&&j<=r)
    {
        if(a[i].val>a[j].val)
        {
            for(int l=i;l<=mid;l++)
                ans=(ans+a[l].val*a[j].val%mod*a[l].id*(n-a[j].id+1)%mod)%mod;
            //printf("%lld\n",ans);
            c[++k].val=a[j].val;c[k].id=a[j].id;
            j++;
        }
        else
        {
            c[++k].val=a[i].val;c[k].id=a[i].id;
            i++;
        }
    }
    while(i<=mid)
    {
        c[++k].val=a[i].val;c[k].id=a[i].id;
        i++;
    }
    while(j<=r)
    {
        c[++k].val=a[j].val;c[k].id=a[j].id;
        j++;
    }
    for(int i=l;i<=r;i++) 
    {
        a[i].id=c[i].id;
        a[i].val=c[i].val;
    }
}
int main()
{
    freopen("multiplication.in","r",stdin);
    freopen("multiplication.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++) 
    {
        a[i].val=read();
        a[i].id=i;
    }
    gb_sort(1,n);
    printf("%lld\n",ans);
    return 0;
}

 

90 pts

發現正是歸併排序統計答案的時候是 O ( n ) 的,使得複雜度升高了;

咱們想能不能優化下:

仍是按照上面的思路,若是在合併的時候有 ai > aj ,則 ai ~ amid 都會與 aj 產生逆序對,那麼 aj 產生的貢獻之和就是:

ai * a* i * ( n - j + 1 ) + ai+1 * aj * ( i+1 )  * ( n - j + 1 ) + …… + amid * aj * mid * ( n - j + 1 )

咱們將 aj * ( n - j + 1 ) 提出來就是:

aj * ( n - j + 1 ) * [ ai * i + ai+1 * ( i+1 ) + ai+2 * ( i+2 ) + amid * mid ]

也就是說,咱們能夠統計在 a以前的全部數中,每一個比 aj 大的數 ai 再乘上 a的下標 i 的和是多少,記爲 sum;

則 sum = ai * i + ai+1 * ( i+1 ) + ai+2 * ( i+2 ) + amid * mid,那麼 a對答案的貢獻就是:sum * aj * ( n - j + 1 )

而後咱們發現這東西能夠用權值樹狀數組來維護:

每一個下標爲 i 的數組維護 a* i 的值是多少,當咱們對原數列的數依次放到樹狀數組的時候,因爲前面的數已經放進去了,咱們能夠去統計有全部比當前數大的數(能夠與當前數構成逆序對的數)它們的 ai * i 的值的和是多少;因爲是權值樹狀數組,因此比當前數要大的數在樹狀數組裏的編號是比當前數的編號大的,因此咱們能夠求後綴和;可是因爲我不會求後綴和,因此咱們能夠把權值樹狀數組的編號反過來存,大的在前面,小的在後面,這樣就轉化成了咱們熟悉的前綴和啦;

可是因爲每一個元素的權值範圍較大,且逆序對的產生只與兩個數的大小關係有關,與兩個數具體是多少無關,因此咱們能夠先離散化;

時間複雜度 O ( nlog n ),指望得分 90 pts;

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
long long read()
{
    char ch=getchar();
    long long a=0,x=1;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') x=-x;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        a=(a<<3)+(a<<1)+(ch-'0');
        ch=getchar();
    }
    return a*x;
}
const long long mod=1e12+7;
const long long N=1e5;
long long n;
long long ans,c[N];
struct node
{
    long long id,val,rank;
}a[N];
bool cmp1(node x,node y)
{
    if(x.val!=y.val)
     return x.val<y.val;
    return x.id<y.id;
}
bool cmp2(node x,node y)
{
    return x.id<y.id;
}
void lsh()                                //離散化
{
    sort(a+1,a+1+n,cmp1);                 //先按照大小排序 
    for(long long i=1;i<=n;i++) a[i].rank=i; //按大小關係給每一個元素分個排名,就是離散化以後的大小 
    sort(a+1,a+1+n,cmp2);                 //再按照在原序列裏的編號排回去 
} 
long long lowbit(long long x)
{
    return x&(-x);
}
long long ask(long long x)                //樹狀數組求[1,x]的和 
{
    long long y=0;
    for(long long i=x;i;i-=lowbit(i)) y=(y+c[i]+mod)%mod;
    return y;
}
void add(long long x,long long y)         //將第x個數加y 
{
    for(long long i=x;i<=n;i+=lowbit(i)) c[i]=(c[i]+y+mod)%mod; 
}
int main()
{
    freopen("multiplication.in","r",stdin);
    freopen("multiplication.out","w",stdout);
    n=read();
    for(long long i=1;i<=n;i++)
    {
        a[i].val=read();                  //每一個元素的大小 
        a[i].id=i;                        //每一個元素在原序列裏的編號(是第幾個) 
    }
    lsh();                                //離散化 
    for(long long i=1;i<=n;i++)           //依次將每一個數丟進樹狀數組 
    {
        long long sum=ask(n-a[i].rank+1); //求一次前綴和,注意這裏大小編號是反着的 
        ans=(ans+sum*a[i].val%mod*(n-a[i].id+1)%mod)%mod;   //算當前元素對答案的貢獻 
        add(n-a[i].rank+1,a[i].id*a[i].val%mod);  //將當前元素丟進樹狀數組裏,維護的是這個數的下標*權值 
    }
    printf("%lld\n",ans%mod);
    return 0; 
}

 

100 pts

不是,我時間複雜度 O ( nlog n ) 跑的飛快啊,怎麼還沒滿分?

有一個小細節須要注意:

咱們的模數是 1012 +7,兩個數相乘極可能爆 long long 的。

這時候咱們就要用到龜速乘了qwq:

龜速乘

龜速乘好像就是來彌補快速冪的 bug 的,雖然計算速度真的龜速。。。

它的原理是這樣的:

假如咱們如今要計算一個簡單的式子:3 * 23

而後咱們將 23 用二進制表示一會兒:( 23 )10 = ( 10111 )2

那麼咱們能夠將 23 寫成這個形式:23 = 20 + 21 + 22 + 24

而後咱們再將其代回原式:3 * 23 = 3 * ( 20 + 21 + 22 + 2) = 3 * 20 + 3 * 21 + 3 * 22 + 3 * 24

而後咱們發現每次加的 3 的係數都是原來的兩倍,這一點與快速冪相似;

代碼實現:

long long slow_pow(long long a,long long b)   //龜速乘計算a*b
{
    long long tot=0;
    while(b)
    {
        if(b&1) tot=(tot+a+mod)%mod; 
        a=(a+a+mod)%mod;                      //每次變爲原來的2倍 
        b>>=1;
    }
    return tot;
}

 

而後套上龜速乘,這個題最後的坑就被咱們填完了,完整AC代碼以下:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
long long read()
{
    char ch=getchar();
    long long a=0,x=1;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') x=-x;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        a=(a<<3)+(a<<1)+(ch-'0');
        ch=getchar();
    }
    return a*x;
}
const long long mod=1e12+7;
const long long N=1e5;
long long n;
long long ans,c[N];
struct node
{
    long long id,val,rank;
}a[N];
long long slow_pow(long long a,long long b)   //龜速乘計算a*b
{
    long long tot=0;
    while(b)
    {
        if(b&1) tot=(tot+a+mod)%mod; 
        a=(a+a+mod)%mod;                      //每次變爲原來的2倍 
        b>>=1;
    }
    return tot;
}
bool cmp1(node x,node y)
{
    if(x.val!=y.val)
     return x.val<y.val;
    return x.id<y.id;
}
bool cmp2(node x,node y)
{
    return x.id<y.id;
}
void lsh()                                //離散化
{
    sort(a+1,a+1+n,cmp1);                 //先按照大小排序 
    for(long long i=1;i<=n;i++) a[i].rank=i; //按大小關係給每一個元素分個排名,就是離散化以後的大小 
    sort(a+1,a+1+n,cmp2);                 //再按照在原序列裏的編號排回去 
} 
long long lowbit(long long x)
{
    return x&(-x);
}
long long ask(long long x)                //樹狀數組求[1,x]的和 
{
    long long y=0;
    for(long long i=x;i;i-=lowbit(i)) y=(y+c[i]+mod)%mod;
    return y;
}
void add(long long x,long long y)         //將第x個數加y 
{
    for(long long i=x;i<=n;i+=lowbit(i)) c[i]=(c[i]+y+mod)%mod; 
}
int main()
{
    freopen("multiplication.in","r",stdin);
    freopen("multiplication.out","w",stdout);
    n=read();
    for(long long i=1;i<=n;i++)
    {
        a[i].val=read();                  //每一個元素的大小 
        a[i].id=i;                        //每一個元素在原序列裏的編號(是第幾個) 
    }
    lsh();                                //離散化 
    for(long long i=1;i<=n;i++)           //依次將每一個數丟進樹狀數組 
    {
        long long sum=ask(n-a[i].rank+1); //求一次前綴和,注意這裏大小編號是反着的 
        ans=(ans+slow_pow(slow_pow(sum,a[i].val)%mod,(n-a[i].id+1))%mod)%mod;   //算當前元素對答案的貢獻 
        add(n-a[i].rank+1,slow_pow(a[i].id,a[i].val)%mod);  //將當前元素丟進樹狀數組裏,維護的是這個數的下標*權值 
    }
    printf("%lld\n",ans%mod);
    return 0; 
}

 

這道題讓我從新溫習了我不熟悉的數據結構,讓我距目標更近了一步呢,最後,CSP 加油!

相關文章
相關標籤/搜索