【NOIP2016普及組】魔法陣的解析——一些神奇的列舉優化
阿新 • • 發佈:2018-12-13
題目大意:給定一個序列,要求求出滿足,且的a,b,c,d的數量.
首先這道題可以直接用一個桶,先把所有數塞進這個桶裡.
然後我們可以開始列舉答案.
但我們發現,所以我們可以省去以為的列舉,做到列舉,實測在洛谷上能拿到85分.
到這一步優化的程式碼如下:
#include<bits/stdc++.h> using namespace std; #define Abigail inline void typedef long long LL; const int N=15000,M=40000; int n,m,x[M+9]; LL num[N+9][4],cnt[N+9]; Abigail into(){ scanf("%d%d",&n,&m); for (int i=1;i<=m;i++){ scanf("%d",&x[i]); cnt[x[i]]++; } } Abigail work(){ for (int a=1;a<=n;a++) for (int b=a+1;b<=n;b++) for (int c=b+1;c<=n;c++){ if (b-a&1||3*b-3*a>=c-b) continue; int d=b-a+c*2>>1; LL ans=cnt[a]*cnt[b]*cnt[c]*cnt[d]; num[a][0]+=ans;num[b][1]+=ans;num[c][2]+=ans;num[d][3]+=ans; } for (int i=1;i<=n;i++) for (int j=0;j<4;j++) num[i][j]/=cnt[i]?cnt[i]:1; } Abigail outo(){ for (int i=1;i<=m;i++){ for (int j=0;j<3;j++) printf("%lld ",num[x[i]][j]); printf("%lld\n",num[x[i]][3]); } } int main(){ into(); work(); outo(); return 0; }
接下來的優化需要將變式,改成.
然後我們設,那麼,.
然後我們在設,那麼就可以畫一個圖:
我們發現,由於所有數都是整數,所以k的最小值為1.
現在我們列舉t和,我們就可以計算出,然後C的最小值為,D的最小值為.
我們發現若t和確定了,那麼我們就可以確定只有k的問題了,我們發現k取[1,n-9t]內的任意一個數都是可以的,那我們就可以維護一個字首和陣列vis[i][t]表示c為i,d為i+t的時候有多少種.
由於這樣會佔用很大記憶體導致MLE,所以我們在推的時候處理,這樣就可以確定c和d的數量了.但是要注意,由於我們要邊處理邊推,所以我們要逆推.
而a和b的確定和c和d很像,換成順推就可以了.
而這樣做的時間複雜度為,還是不能拿到滿分.
接下來我們就進行一些細節的優化,我們發現,而,所以我們可以發現其實,所以t只需要列舉到就可以了.但是這樣寫程式碼會有除法,比較慢,所以我們寫成.
那麼這個演算法的時間複雜度就是,可以過了,但可能需要一定的常數優化(然而並沒有什麼可以優化的地方).
所以大致的演算法流程如下:
1.列舉.
2.列舉,可以推出C=D-t,之後可以設A為A的最大值,B為B的最大值,得到B=C-6t-1,A=6t-1.
3.在2的同時,記錄一個sum初始為0,表示當前D時,A和B的情況有多少種.
4.同樣,列舉.
5.同樣,到這處理字首和.
程式碼如下:
#include<bits/stdc++.h> using namespace std; #define Abigail inline void typedef long long LL; const int N=15000,M=40000; int n,m,x[M+9],num[4][N+9],cnt[N+9]; Abigail into(){ scanf("%d%d",&n,&m); for (int i=1;i<=m;i++){ scanf("%d",&x[i]); cnt[x[i]]++; } } Abigail work(){ int sum,A,B,C,D; for (int t=1;t*9<n;t++){ sum=0; for (D=9*t-2;D<=n;D++){ C=D-t; B=C-6*t-1; A=B-2*t; //知道D,計算出A,B,C sum+=cnt[A]*cnt[B]; //計算當前A和B的情況 num[2][C]+=cnt[D]*sum; //num[2][C]+=cnt[A]*cnt[B]*cnt[C] num[3][D]+=cnt[C]*sum; //num[3][D]+=cnt[A]*cnt[B]*cnt[D] } sum=0; for (A=n-9*t-1;A;A--){ B=A+2*t; C=B+6*t+1; D=C+t; //知道A,計算出B,C,D sum+=cnt[C]*cnt[D]; //計算當前C和D的情況 num[0][A]+=cnt[B]*sum; //num[0][A]+=cnt[B]*cnt[C]*cnt[D] num[1][B]+=cnt[A]*sum; //num[1][B]+=cnt[A]*cnt[C]*cnt[D] } } } Abigail outo(){ for (int i=1;i<=m;i++){ for (int j=0;j<3;j++) printf("%d ",num[j][x[i]]); printf("%d\n",num[3][x[i]]); } } int main(){ into(); work(); outo(); return 0; }