题意:大魔法师有\(m\)个魔法物品,编号分别为\(1,2,...,m\)。每个物品具有一个魔法值,我们用\(X_i\)表示编号为i的物品的魔法值。每个魔法值Xi是不超过n的正整数,可能有多个物品的魔法值相同。大魔法师认为,当且仅当四个编号为\(a,b,c,d\)的魔法物品满足\(x_a<x_b<x_c<x_d,X_b-X_a=2(X_d-X_c)\),并且\(x_b-x_a<(x_c-x_b)/3\)时,这四个魔法物品形成了一个魔法阵,他称这四个魔法物品分别为这个魔法阵的\(A\)物品,\(B\)物品,\(C\)物品,\(D\)物品。现在,大魔法师想要知道,对于每个魔法物品,作为某个魔法阵的\(A\)物品出现的次数,作为\(B\)物品的次数,作为\(C\)物品的次数,和作为\(D\)物品的次数。\(n<=15000,m<=40000.\)
分析:拿到题就想到直接爆搜啊.各种剪枝啊,枚举优化,分数从\(0->40->50->55\),实在是尽力了...看题解说暴力可以优化到\(85\)分,看来是我的暴力太暴力了.
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
int x=0,o=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')o=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*o;
}
const int N=40005;
int n,m;
int b[5],bj[N],ans[N][5];
struct ppx{int id,val;}a[N];
inline bool cmp(ppx x,ppx y){return x.val==y.val?x.id<y.id:x.val<y.val;}
inline void dfs(int last,int now){
//这里面的剪枝都是根据那三个式子推出来的,没有什么思维难度,懒得解释了
if(now==3){
if((a[b[2]].val-a[b[1]].val)&1)return;
if(b[2]<=b[1])return;
if(a[b[1]].val==a[b[2]].val)return;
}
if(now==4){
if(!bj[(a[b[2]].val-a[b[1]].val)/2+a[b[3]].val])return;
if(b[3]<=b[2])return;
if(a[b[2]].val==a[b[3]].val)return;
if(3*(a[b[2]].val-a[b[1]].val)>=a[b[3]].val-a[b[2]].val)return;
}
if(now==5){
if(b[4]<=b[3])return;
if(a[b[3]].val==a[b[4]].val)return;
if(a[b[2]].val-a[b[1]].val!=2*(a[b[4]].val-a[b[3]].val))return;
if(b[1]<b[2]&&b[2]<b[3]&&b[3]<b[4]){
for(int i=1;i<=4;++i)++ans[a[b[i]].id][i];
}
return;
}
if(m-last+1<=4-now)return;
for(int i=last;i<=m;++i){
b[now]=i;
dfs(last+1,now+1);
}
}
int main(){
n=read();m=read();
for(int i=1;i<=m;++i)a[i].id=i,a[i].val=read(),bj[a[i].val]=1;//标记这个数出现过
sort(a+1,a+m+1,cmp);//按照权值排序,优化枚举顺序
dfs(1,1);
for(int i=1;i<=m;++i){
printf("%d %d %d %d\n",ans[i][1],ans[i][2],ans[i][3],ans[i][4]);
}
return 0;
}
然后就去看题解了,竟然是一道数学题啊.思想极其巧妙,反正我是不可能想到的.我就讲一下前缀和优化思想吧,我自己看题解的时候就是这里卡了很久.
当我们从小到大枚举\(D(D_1,D_2......\))的时候,每次的\(A(A_1,A_2......)\)和\(B(B_1,B_2......)\)都是通过\(D(D_1,D_2......\))推出来的,因为推完式子之后我们已经得到了只要\(X_C-X_B>6*t+k\),就一定能够构成魔法阵.所以如果\(A_1,B_1,C_1,D_1\)能够构成魔法阵,那么\(A_1,B_1,C_2,D_2\)也一定能够构成魔法阵(因为随着\(D\)的增大,C也在增大,所以前面的A,B一定能跟现在的C,D构成魔法阵),所以我们才可以维护\(A,B\)的前缀和.
\(C,D\)的后缀和也是一个思想咯.
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
int x=0,o=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')o=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*o;
}
const int N=15005;
const int M=40005;
int x[M],bj[N],a[N],b[N],c[N],d[N];
int main(){
int n=read(),m=read();
for(int i=1;i<=m;++i){
x[i]=read();
++bj[x[i]];
}
for(int t=1;9*t<n;++t){
int sum=0;
for(int D=9*t+1;D<=n;++D){
int A=D-9*t-1,B=A+2*t,C=D-t;
sum+=bj[A]*bj[B];
c[C]+=sum*bj[D];
d[D]+=sum*bj[C];
}
sum=0;
for(int A=n-9*t-1;A;--A){
int B=A+2*t,C=B+6*t+1,D=C+t;
sum+=bj[C]*bj[D];
a[A]+=sum*bj[B];
b[B]+=sum*bj[A];
}
}
for(int i=1;i<=m;++i)
printf("%d %d %d %d\n",a[x[i]],b[x[i]],c[x[i]],d[x[i]]);
return 0;
}