神题——魔法阵

题目描述

六十年一次的魔法战争就要开始了,大魔法师准备从附近的魔法场中汲取魔法能量。

大魔法师有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 150001n15000,1 \le m \le 400001m40000,1 \le Xi \le n1Xin。每个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 }

 

转载于:https://www.cnblogs.com/Misaki-Mei/p/7711739.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值