1. 程式人生 > >【NOIP2016普及組】魔法陣的解析——一些神奇的列舉優化

【NOIP2016普及組】魔法陣的解析——一些神奇的列舉優化

題目大意:給定一個序列x_i,要求求出滿足\bg_white x_a<x_b<x_c<x_dx_b-x_a=2(x_d-x_c)x_b-x_a<\frac{x_c-x_b}{3}的a,b,c,d的數量.

首先這道題可以直接用一個桶,先把所有數塞進這個桶裡.

然後我們可以開始O(n^4)列舉答案.

但我們發現x_d=\frac{x_b-x_a+2x_c}{2},所以我們可以省去以為的列舉,做到O(n^3)列舉,實測在洛谷上能拿到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;
} 

接下來的優化需要將x_b-x_a<\frac{x_c-x_b}{3}變式,改成3(x_b-x_a)<x_c-x_b.

然後我們設t=x_d-x_c,那麼2t=x_b-x_a6t<x_c-x_b.

然後我們在設6t+k=x_c-x_b,那麼就可以畫一個圖:

我們發現x_d-x_a=9t+k,由於所有數都是整數,所以k的最小值為1.

現在我們列舉t和x_a,我們就可以計算出x_b=x_a+2t,然後C的最小值為x_a+2t+6t+k_{min}=x_a+8t+1,D的最小值為x_a+8t+1+t=x_a+9t+1.

我們發現若t和x_a確定了,那麼我們就可以確定只有k的問題了,我們發現k取[1,n-9t]內的任意一個數都是可以的,那我們就可以維護一個字首和陣列vis[i][t]表示c為i,d為i+t的時候有多少種.

由於這樣會佔用很大記憶體導致MLE,所以我們在推的時候處理,這樣就可以確定c和d的數量了.但是要注意,由於我們要邊處理邊推,所以我們要逆推.

而a和b的確定和c和d很像,換成順推就可以了.

而這樣做的時間複雜度為O(n^2),還是不能拿到滿分.

接下來我們就進行一些細節的優化,我們發現x_a+9t+k<=n,而x_a,k>=1,所以我們可以發現其實t<=\frac{n-2}{9},所以t只需要列舉到\frac{n-2}{9}就可以了.但是這樣寫程式碼會有除法,比較慢,所以我們寫成9t<=n-2.

那麼這個演算法的時間複雜度就是O(\frac{n^2}{9}),可以過了,但可能需要一定的常數優化(然而並沒有什麼可以優化的地方).

所以大致的演算法流程如下:

1.列舉t=1\rightarrow \frac{n^2}{9}.

2.列舉D=n\rightarrow 2+9t,可以推出C=D-t,之後可以設A為A的最大值,B為B的最大值,得到B=C-6t-1,A=6t-1.

3.在2的同時,記錄一個sum初始為0,表示當前D時,A和B的情況有多少種.

4.同樣,列舉A=n-9t-1\rightarrow 1.

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;
}