神题——魔法阵
题目描述
六十年一次的魔法战争就要开始了,大魔法师准备从附近的魔法场中汲取魔法能量。
大魔法师有m个魔法物品,编号分别为1,2,...,m。每个物品具有一个魔法值,我们用Xi表示编号为i的物品的魔法值。每个魔法值Xi是不超过n的正整数,可能有多个物品的魔法值相同。
大魔法师认为,当且仅当四个编号为a,b,c,d的魔法物品满足xa<xb<xc<xd,Xb-Xa=2(Xd-Xc),并且xb-xa<(xc-xb)/3时,这四个魔法物品形成了一个魔法阵,他称这四个魔法物品分别为这个魔法阵的A物品,B物品,C物品,D物品。
现在,大魔法师想要知道,对于每个魔法物品,作为某个魔法阵的A物品出现的次数,作为B物品的次数,作为C物品的次数,和作为D物品的次数。
输入输出格式
输入格式:
输入文件的第一行包含两个空格隔开的正整数n和m。
接下来m行,每行一个正整数,第i+1行的正整数表示Xi,即编号为i的物品的魔法值。
保证1 \le n \le 150001≤n≤15000,1 \le m \le 400001≤m≤40000,1 \le Xi \le n1≤Xi≤n。每个Xi是分别在合法范围内等概率随机生成的。
输出格式:
共输出m行,每行四个整数。第i行的四个整数依次表示编号为i的物品作 为A,B,C,D物品分别出现的次数。
保证标准输出中的每个数都不会超过10^9。
每行相邻的两个数之间用恰好一个空格隔开。
输入样例1:
30 8 1 24 7 28 5 29 26 24
输出样例1:
4 0 0 0 0 0 1 0 0 2 0 0 0 0 1 1 1 3 0 0 0 0 0 2 0 0 2 2 0 0 1 0
输入样例2:
15 15 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
输出样例2:
5 0 0 0 4 0 0 0 3 5 0 0 2 4 0 0 1 3 0 0 0 2 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 2 1 0 0 3 2 0 0 4 3 0 0 5 4 0 0 0 5
--------------------------------------------------------
说实话,当我看到这道题时我呆住了,没办法,我水平低,就去看题解,当我看到题解时,我就彻底石化了……
为什么题解都长得一个样……
于是……
我的代码,就很自然的长的不免有点相似……
思路是这样的:
a=xa,b=xb,c=xc,d=xd
思路一:暴力四重for循环……不清楚能不能拿10分
思路二:暴力三重for循环……得分应该比思路一多点儿
思路三:我们可以枚举 b-c 的值 t ,所以就可以暴力枚举 b,c,用t表示出a d,但……依然会炸……
思路四:由于题目给出了n的范围,于是n自然有很独特(变态)的用处……那么问题来了,n是干什么用的呢?n可以用来限制t(t=d-c)的范围
因为(b-a)=2(c-d),b-a<(c-b)/3,所以可以推出c-b>6t,所以约摸一下,t大约取到n/9就可以了(这就是这道题的神级优化),
有了这个关系,我们可以暴力枚举a所有可能的值,用t和a表示b c d,在枚举一下d所有可能值,同样表示,用乘法原理累加,最后输出答案
至于很多细节,在代码里解释(反正很暴力,很坑,可能是我太弱了)
代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<string> 4 #include<algorithm> 5 #include<cmath> 6 #include<iostream> 7 using namespace std; 8 int aa,bb,cc,dd,num,sum; 9 int a[50000],m,n,t,tong[50000],numa[50000],numb[50000],numc[50000],numd[50000]; 10 11 int main(){ 12 scanf("%d %d",&n,&m); 13 for(int i=1;i<=m;i++) scanf("%d",&a[i]); 14 for(int i=1;i<=m;i++) tong[a[i]]++;//这里用到了桶排,比较快,比较方便,顺便记录一下个数 15 for(int t=1;t<=n/9;t++){ 16 num=0; //因为同一个a会对应多个c,所以用num累计一下个数,怎么用一会说 17 sum=0; //同理 18 for(int a=n-9*t-1;a>=1;a--){//计算得a最大为n-9*t-1,从后向前找,方便结果的累计 19 bb=a+t*2;//表示b 20 cc=a+8*t+1;//表示c,因为c有很多值,这里取最小的值,然后在一次次寻找中找到c的所有值,这里比较抽象,比较坑 21 dd=a+9*t+1;//表示d 22 num+=tong[cc]*tong[dd]; //乘法原理。num累计的作用:因为a从后向前搜,故后面a可用的值,前面的a也一定能用,因为对应的c不一定 23 numa[a]+=tong[bb]*num;//这样就可以通过累加的方式,省去对c的枚举 24 numb[bb]+=tong[a]*num;//乘法原理 25 } 26 for(int d=9*t+2;d<=n;d++){ 27 aa=d-9*t-1;//同理,d从前向后搜 28 bb=2*t+aa; 29 cc=d-t; 30 sum+=tong[aa]*tong[bb]; 31 numc[cc]+=tong[d]*sum; 32 numd[d]+=tong[cc]*sum; 33 } 34 } 35 for(int i=1;i<=m;i++){ //输出 36 cout<<numa[a[i]]<<" "; 37 cout<<numb[a[i]]<<" "; 38 cout<<numc[a[i]]<<" "; 39 cout<<numd[a[i]]<<endl; 40 } 41 return 0; 42 }