【水題一道】7的倍數

水題解析——7的倍數


題面:

【題目名稱】7的倍數
【時間限制】每個測試點300ms
【空間限制】128M

【題目描述】
給定一個各項均不相同且項數爲n的正整數數列:A1,A2,A3,……,An.從中選取若干個數(至少一個),使這些數之和爲7的倍數.求共有多少種不同的選法.
測試數據分爲兩類:
A類:數列由輸入數據直接給出;
B類:給定k(k<=100000)個正整數:p1,p2,p3,……,pk.數列由通項公式:
An=p1^n+p2^n+p3^n+……+pk^n確定.
【輸入格式】
第一行,兩個整數n,k:其中n表示數列項數.若k=0,則表示該組數據爲A類數據;若k>0,則表示該組數據爲B類數據.
第二行,若爲A類數據,則接下來有n個互不相同的正整數,其中第i個數表示數列的第i項;若爲B類數據,則接下來有k個正整數:p1,p2,p3,…,pk.(具體含義見題目描述)
【輸出格式】
一個非負整數,表示共有多少種選法.由於答案可能很大,請mod 1000000007後輸出.
【樣例輸入1】
6 0
1 2 3 4 5 6
【樣例輸出1】
9
【樣例輸入2】
4 2
3 5
【樣例輸出2】
2
【數據範圍與約定】
對於20%的數據,k=0,1<=n<=20;
對於60%的數據,k=0,1<=n<=100000;
對於另 40%的數據,1<=k<=10^5,1<=n<=10^18.


數據:

這題數據並沒有什麼特別之處,在此就不提供數據了。


題解:

20分算法:搜索

只處理A類數據.

  1. 很裸很裸的暴力搜索;
  2. 依次枚舉每個數選與不選;
  3. 然後驗證並計數;
  4. 時間複雜度O(2^n).

20分暴搜代碼:

#include<cstdio>
#define mod 1000000007
int n,k,A[1100],cnt=0;
void dfs(int p,int num)
{
    if(p>n)
    {
        cnt+=(num%7==0);
        if(cnt>mod)
        {
            cnt-=mod;
        }
        return ; 
    }
    dfs(p+1,num);
    dfs(p+1,num+A[p]);
    return ;
}
int main()
{
    scanf("%d%d",&n,&k);//只處理A類數據; 
    for(int i=1;i<=n;i++)
    {
         scanf("%d",&A[i]);
    }
    dfs(1,0);//深搜;
    printf("%d",cnt-1);//排除一個也不選的情況; 
    return 0;
}

60分算法:揹包

在爆搜的時候,我們討論每個數選與不選的情況,這給人一種揹包問題的感覺.事實上,這還真是個揹包問題.
狀態:f[i][j]表示從前i個數中選取若干個數,使它們之和mod 7得j的方案數。
那麼我們的轉移方程是:
f[i][j]=f[i-1][j]+f[i-1][(j-(A[i]%7)+7)%7]..
後面那一坨是什麼意思?
舉個例子,比如遇到A[i]%7==4時:
那麼f[i][5]=f[i-1][5]+f[i-1][1],(注:(5-4+7)%7=8%7=1).
f[i][2]=f[i-1][2]+f[i-1][5],(注:(2-4+7)%7=5%7=5).
加7再mod 7是爲了將減得的負的餘數,轉爲正的.
當然我們還可以用滾動數組,優化空間複雜度.
具體實現參見: 60分揹包代碼:

#include<cstdio>
#define mod 1000000007
int n,k,f[2][7];
int main()
{
    //仍然只處理A類數據; 
    scanf("%d%d",&n,&k);
    f[0][0]=1;//表示餘數爲0的方案有1種; 
    for(int i=1,x;i<=n;i++) 
    {
        scanf("%d",&x);
        x%=7;//直接取mod 
        for(int j=0;j<=6;j++)
        {
            f[i&1][j]=f[i&1^1][(j-x+7)%7];//餘數爲j的可以由余數爲(j-x+7)%7的轉化而來; 
        }//因爲更新過程是一個環狀的過程,不能像裸的揹包那樣原地更新覆蓋,故採用滾動. 
        for(int j=0;j<=6;j++)
        {
            f[i&1][j]+=f[i&1^1][j];
            f[i&1][j]%=mod;
        }
    }
    printf("%d",f[n&1][0]-1);//排除一個也不選的情況; 
    return 0;
}

100分算法:揹包+矩陣乘法

不難發現:

  • 數據很大
  • 時間複雜度應該是O(log n)的,可能還會有常數
  • 揹包的轉移方程只與A[i]%7的值有關,而這個總共只有7種情況
  • 相同餘數對應相同的轉移方程
  • 轉移方程可以寫成矩陣乘法的形式
  • 對於A[i]我們只關心它們%7的值,而不關心其本身的大小
  • 由費馬小定理知,A[i]%7的餘數呈週期變化,最小正週期<=6
  • 根據這些我們已經可以寫出一個O(7^4 * log n)的算法了

不過還有一些細節,這些會在100分代碼中呈現:

#include<cstdio>
#define ll long long
#define mod 1000000007
ll n,r[10],xi[10],tmp[10],cnt[10];
/*
r[i]表示Ai mod 7的餘數;
xi[i]表示在p[1~k]中mod 7餘i的數的個數;
tmp[i]是一個臨時變量,用處見後文;
cnt[i]表示在n個數中,mod 7餘i的數的個數,是矩陣乘法的指數;
*/ 
ll A[10][10],ANS[10][10],C[10][10];
//矩陣:A--底數,ANS--答案,C--臨時中轉; 
int k;
int main()
{
    scanf("%I64d%d",&n,&k);
    if(k==0)
    {
        for(int i=1,x;i<=n;i++)
        {
            scanf("%d",&x);
            cnt[x%7]++;
        }
    }//A類; 
    else
    {
        for(int i=1,x;i<=k;i++)
        {
            scanf("%d",&x);
            xi[x%7]++;
        }//記錄pk數列中mod 7得i的有多少個;不記也可.
        for(int i=0;i<=6;i++)
        {
            tmp[i]=1;
        }
        for(int i=1;i<=6;i++)
        {
            for(int j=0;j<=6;j++)
            {
                tmp[j]=tmp[j]*j%7;
                r[i]=(r[i]+xi[j]*tmp[j])%7;
            }
        }//處理循環節1~6的情況; 
        int  rr=n%6;
        for(int i=1;i<=6;i++)
        {
            if(i<=rr)
            {
                cnt[r[i]]+=n/6+1;//利用循環的性質計數; 
            }
            else
            {
                cnt[r[i]]+=n/6;//同上; 
            }
        }
    }
    for(int i=0;i<=6;i++)
    {
        ANS[i][i]=1LL;
    }//賦初始值; 
    for(int i=0;i<=6;i++)//討論各個餘數的情況; 
    {
        for(int j=0;j<=6;j++)
        {
            for(int k=0;k<=6;k++)
            {
                A[j][k]=0;
            }
        }//清零底數; 
        for(int j=0;j<=6;j++)
        {
            A[j][j]+=1LL;
            A[(j-i+7)%7][j]+=1LL;
        }//構建矩陣; 
        for(;cnt[i];cnt[i]=cnt[i]>>1)
        {
            if(cnt[i]&1)
            {
                for(int s1=0;s1<=6;s1++)
                {
                    for(int s2=0;s2<=6;s2++)
                    {
                        C[s1][s2]=0LL;
                        for(int s3=0;s3<=6;s3++)
                        {
                            C[s1][s2]=(C[s1][s2]+ANS[s1][s3]*A[s3][s2])%mod;
                        }
                    }
                }
                for(int s1=0;s1<=6;s1++)
                {
                    for(int s2=0;s2<=6;s2++)
                    {
                        ANS[s1][s2]=C[s1][s2];
                    }
                }
            }//ANS=ANS*A%mod; 
            for(int s1=0;s1<=6;s1++)
            {
                for(int s2=0;s2<=6;s2++)
                {
                    C[s1][s2]=0LL;
                    for(int s3=0;s3<=6;s3++)
                    {
                        C[s1][s2]=(C[s1][s2]+A[s1][s3]*A[s3][s2])%mod;
                    }
                }
            }
            for(int s1=0;s1<=6;s1++)
            {
                for(int s2=0;s2<=6;s2++)
                {
                    A[s1][s2]=C[s1][s2];
                }
            }//A=A*A%mod;
        }//快速冪; 
    }
    printf("%I64d",ANS[0][0]-1);//排除一個也不選的情況; 
    return 0;
}       

至此這道水題就講完了.

發佈了31 篇原創文章 · 獲贊 6 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章