1. 程式人生 > >Luogu P3941 入陣曲【字首和】By cellur925

Luogu P3941 入陣曲【字首和】By cellur925

題目傳送門

題目大意:給你一個\(n\)*\(m\)的矩陣,每個位置都有一個數,求有多少不同的子矩陣使得矩陣內所有數的和是\(k\)的倍數。

資料範圍給的非常友好233,期望得到的暴力分:75分。前12個點可以用\(O(n^4)\)演算法水過,對於\(<=400\)的有特殊性質2的資料,我們還可以嘗試苟一下,開始用了一個什麼鬼方法(?),其實我們只要列舉所有可能的矩形面積判斷一下是否滿足條件再加上這種矩形面積的所有可能數就行啦。

#include<cstdio>
#include<algorithm>

using namespace std;
typedef long long ll;

int n,m,k;
ll ans,mapp[450][450];

ll gcd(ll a,ll b)
{
    return b ? gcd(b,a % b) : a ; 
}

void calc(ll a,ll lima,ll b,ll limb)
{
    ll cnt1=lima-a+1;
    ll cnt2=limb-b+1;
    ans+=cnt1*cnt2;
}

int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%lld",&mapp[i][j]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            mapp[i][j]+=mapp[i-1][j]+mapp[i][j-1]-mapp[i-1][j-1];
    if(n<=80||m<=2)
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                for(int l=1;l<=n&&i-l+1>=1;l++)
                    for(int r=1;r<=m&&j-r+1>=1;r++)
                    {
                        int ii=i-l+1,jj=j-r+1;
                        ll sum=mapp[i][j]+mapp[ii-1][jj-1]-mapp[ii-1][j]-mapp[i][jj-1];
                        if(sum%k==0) ans++;
                    }
        printf("%lld\n",ans);
        return 0;
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if((i*j*mapp[1][1])%k==0) calc(i,n,j,m);
    printf("%lld\n",ans);
    return 0;
}

其實做這道題的時候感覺和昨天考試T1比較像的,我們可以很容易的想出\(O(n^4)\)演算法,再根據一些性質(如單調性)優化到\(O(n^3)\)。本題要求的複雜度同樣是\(O(n^3)\)

由“\(k\)的倍數”我們可以想到另一道題:ZR某次普及膜底賽當時chengni dalao給我講了子共七的思想,雖說後來講了子共七那道原題,還是沒A==。

我們考慮在一維序列上的情況,若\(sum[i]\)\(k\)等於\(A\),之後出現了一個\(sum[j]\)\(k\)也等於\(A\),那麼顯然有\([i+1,j]\)這部分的和是\(k\)的倍數(模\(k\)\(0\))。

我們可以推廣到矩陣上的情況,像昨天一樣列舉矩陣的上下界,再列舉一個左右邊界,統計餘數個數,這樣能把複雜度壓到\(O(n^3)\)

。每次的計數陣列要清空,但是用\(memset\)會超時,不妨用陣列記錄一下空間換時間。

#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;
typedef long long ll;

int n,m,moder;
ll ans,f[1000][1000],tong[1000090],b[1000090];

int main()
{
    scanf("%d%d%d",&n,&m,&moder);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%lld",&f[i][j]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            (f[i][j]+=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+moder)%=moder;
    for(int i=0;i<n;i++)//注意是從0開始列舉 
        for(int j=i+1;j<=n;j++)
        {
            tong[0]=1;
            for(int k=1;k<=m;k++)
            {
                b[k]=(f[j][k]-f[i][k]+moder)%moder;
                ans+=tong[b[k]]++;
            }
            for(int k=1;k<=m;k++) tong[b[k]]=0;
        }
    printf("%lld\n",ans);
    return 0;
}

矩陣+字首和思路:
發現題目中的單調性
當問“倍數”時,考慮取膜,與字首和結合計數

另外敲敲說一句 看到入陣曲/星空/將軍令這三首題的時候激動了一下==!