[NOIP2016PJ]魔法阵
今天模拟赛的题,,,唯一没有Giao出来的题(不然我就AKIOI了~)
最开始没想到数学题,把所有部分分都说一遍吧:
35分:纯暴力O(M^4)枚举,对于每一组a,b,c,d验证其是否合法。
60分:经过读题,不难发现a,b,c,d单调递增,可以考虑对其进行排序后再暴力枚举,枚举量减少近一半。
85分:对xb-xa=2(xd-xc)进行分析,可以得到以下公式:double((xb-xa+2xc)/2)=double(xd),再查找是否存在xd,这样我们只需枚举a,b,c,时间复杂度是O(M^3)
100分:依旧是对xb-xa=2(xd-xc)进行分析,我们设t=xd-xc,则xb-xa=2⋅t;再分析第二个条件Xb−Xa<(Xc−Xb)/3,我们可以得到Xc−Xb>6⋅t,我们给他补全成等号,就是Xc−Xb=6⋅t+k
所以这四个数在数轴上的排列如图所示(图片来自博客园)
所以我们会有一个不成熟的思路:在1-n/9范围内枚举t,把a,b,c,d拿t表示出来。
那么如何计算呢?枚举D。当我们枚举到一个D值的时候,与之对应的C值是确定的(不受k影响),而A值和B值却不一定。因此我们可以找到最大的与之对应的A值B值。
但是有可能会存在一组AB值要比当前计算到的小,怎么办呢?不妨设有可能存在的比最大值小的A值为A1,B值为B1,计算到的为A2和B2
当A1<A2&&B1<B2时,只要A2和B2能组成魔法阵,A1和B1一定可以(k只是大于0的数,而对k的上界没有限制,当我们把k放大时,就可以构造出A1和B1了)。
由于是顺序枚举,所以我们可以记录一下之前有多少组合法解(类似于前缀和),最后再用乘法原理计算。同样的方法,我们从A的上界往A的下界枚举记录后缀和然后计算即可。
下面给出参考代码:
1 // luogu-judger-enable-o2 2 #include<iostream> 3 #include<cstdio> 4 #include<cstring> 5 #define N 50005 6 #define M 50005 7 using namespace std; 8 int n,m,ans[M][10],num[M],a[M],A,B,C,D; 9 int read() 10 { 11 int x=0,f=1;char ch=getchar(); 12 while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=getchar();} 13 while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} 14 if(f)return x;return -x; 15 } 16 int main() 17 { 18 n=read();m=read(); 19 for(int i=1;i<=m;i++) 20 { 21 a[i]=read(); 22 num[a[i]]++; 23 } 24 for(int t=1;t*9<n;t++) 25 { 26 int sum=0; 27 for(D=9*t+2;D<=n;D++) 28 { 29 C=D-t; 30 B=C-6*t-1; 31 A=B-2*t; 32 sum+=num[A]*num[B]; 33 ans[C][3]+=num[D]*sum; 34 ans[D][4]+=num[C]*sum; 35 } 36 sum=A=B=C=D=0; 37 for(A=n-t*9-1;A>=1;A--) 38 { 39 B=A+2*t; 40 C=B+6*t+1; 41 D=C+t; 42 sum+=num[C]*num[D]; 43 ans[A][1]+=num[B]*sum; 44 ans[B][2]+=num[A]*sum; 45 } 46 } 47 for(int i=1;i<=m;i++) 48 { 49 for(int j=1;j<=4;j++) 50 { 51 cout<<ans[a[i]][j]<<" "; 52 } 53 cout<<endl; 54 } 55 return 0; 56 }