P2119 [NOIP 2016 普及组] 魔法阵
P2119 [NOIP 2016 普及组] 魔法阵
题目背景
NOIP2016 普及组 T4
题目描述
六十年一次的魔法战争就要开始了,大魔法师准备从附近的魔法场中汲取魔法能量。
大魔法师有 mmm 个魔法物品,编号分别为 1,2,…,m1,2,\ldots,m1,2,…,m。每个物品具有一个魔法值,我们用 XiX_iXi 表示编号为 iii 的物品的魔法值。每个魔法值 XiX_iXi 是不超过 nnn 的正整数,可能有多个物品的魔法值相同。
大魔法师认为,当且仅当四个编号为 a,b,c,da,b,c,da,b,c,d 的魔法物品满足 Xa<Xb<Xc<Xd,Xb−Xa=2(Xd−Xc)X_a<X_b<X_c<X_d,X_b-X_a=2(X_d-X_c)Xa<Xb<Xc<Xd,Xb−Xa=2(Xd−Xc),并且 Xb−Xa<(Xc−Xb)/3X_b-X_a<(X_c-X_b)/3Xb−Xa<(Xc−Xb)/3 时,这四个魔法物品形成了一个魔法阵,他称这四个魔法物品分别为这个魔法阵的 AAA 物品,BBB 物品,CCC 物品,DDD 物品。
现在,大魔法师想要知道,对于每个魔法物品,作为某个魔法阵的 AAA 物品出现的次数,作为 BBB 物品的次数,作为 CCC 物品的次数,和作为 DDD 物品的次数。
输入格式
第一行包含两个空格隔开的正整数 n,mn,mn,m。
接下来 mmm 行,每行一个正整数,第 i+1i+1i+1 行的正整数表示 XiX_iXi,即编号为 iii 的物品的魔法值。
保证 1≤n≤150001 \le n \le 150001≤n≤15000,1≤m≤400001 \le m \le 400001≤m≤40000,1≤Xi≤n1 \le X_i \le n1≤Xi≤n。每个 XiX_iXi 是分别在合法范围内等概率随机生成的。
输出格式
共 mmm 行,每行 444 个整数。第 iii 行的 444 个整数依次表示编号为 iii 的物品作 为 A,B,C,DA,B,C,DA,B,C,D 物品分别出现的次数。
保证标准输出中的每个数都不会超过 10910^9109。每行相邻的两个数之间用恰好一个空格隔开。
输入输出样例 #1
输入 #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
输入 #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
说明/提示
【样例解释 111】
共有 555 个魔法阵,分别为:
- 物品 1,3,7,61,3,7,61,3,7,6,其魔法值分别为 1,7,26,291,7,26,291,7,26,29;
- 物品 1,5,2,71,5,2,71,5,2,7,其魔法值分别为 1,5,24,261,5,24,261,5,24,26;
- 物品 1,5,7,41,5,7,41,5,7,4,其魔法值分别为 1,5,26,281,5,26,281,5,26,28;
- 物品 1,5,8,71,5,8,71,5,8,7,其魔法值分别为 1,5,24,261,5,24,261,5,24,26;
- 物品 5,3,4,65,3,4,65,3,4,6,其魔法值分别为 5,7,28,295,7,28,295,7,28,29。
以物品 555 为例,它作为 AAA 物品出现了 111 次,作为 BBB 物品出现了 333 次,没有作为 CCC 物品或者 DDD 物品出现,所以这一行输出的四个数依次为 1,3,0,01,3,0,01,3,0,0。
此外,如果我们将输出看作一个 mmm 行 444 列的矩阵,那么每一列上的 mmm 个数之和都应等于魔法阵的总数。所以,如果你的输出不满足这个性质,那么这个输出一定不正确。你可以通过这个性质在一定程度上检查你的输出的正确性。
【数据规模】

70分
排序,
四次循环(用给的等式剪枝)
排序
输出
#include <bits/stdc++.h>
using namespace std;
const int N=4e4+5;
struct node{int id,v,m[5];node(){memset(m,0,sizeof(m));};node(int idx,int vx){id=idx,v=vx;memset(m,0,sizeof(m));};bool operator<(const node& x)const{if(v==x.v)return id<x.id;else return v<x.v;}//const &避免拷贝,直接操作原元素,还不能修改
}obj[N];
bool cmp(const node& a,const node& b){return a.id<b.id;}
int n,m;
void view(){cout<<"显示各物质"<<endl;for(int i=1;i<=m;i++)cout<<obj[i].id<<"\t";cout<<endl;for(int i=1;i<=m;i++)cout<<obj[i].v<<"\t";cout<<endl;
}
void view2(){//cout<<"显示各次数"<<endl;for(int i=1;i<=m;i++){for(int j=1;j<=4;j++)cout<<obj[i].m[j]<<" ";cout<<endl;}}
int main(){//freopen("data.cpp","r",stdin);cin>>n>>m;for(int i=1;i<=m;i++){int x;cin>>x;obj[i]=node(i,x);}//view();sort(obj+1,obj+m+1);//view();/*a<b<c<db-a<(c-b)/3b-a=2*(d-c)*/for(int i1=1;i1<=m;i1++)for(int i2=i1+1;i2<=m;i2++){if(obj[i2].v==obj[i1].v)continue;if((obj[i2].v-obj[i1].v)%2)continue;for(int i3=i2+1;i3<=m;i3++){//if((obj[i3].v-obj[i2].v)%3)continue;没说等于,所以不能被3整除也可以 if(obj[i2].v==obj[i3].v)continue;if((obj[i2].v-obj[i1].v)*3>=(obj[i3].v-obj[i2].v))continue;for(int i4=i3+1;i4<=m;i4++){if(obj[i4].v==obj[i3].v)continue;if(obj[i2].v-obj[i1].v!=2*(obj[i4].v-obj[i3].v))continue;obj[i1].m[1]++; obj[i2].m[2]++; obj[i3].m[3]++; obj[i4].m[4]++; }}}sort(obj+1,obj+m+1,cmp);//view();view2();return 0;
}
学习洛谷题解100分
等式分析转换观察作图掌握规律
发现是ab和cd分别一一对应,
ab对应众多众多cd,而且随着a的减少对应cd线性增加,可以后缀和
cd对应众多ab,随着d增加ab也会增加,前缀和。
厉害,前缀和有用。
#include <bits/stdc++.h>
using namespace std;
const int N=15000,//数值极限 M=40001;//数字数量的极限,不能写成4e4=40000
int n,//数字的上限 m,//数的数量v[M],//每个数的数值cnt[N],//每个数出现的次数 A[N],B[N],C[N],D[N];//每个数成为abcd组合的次数
int main(){//freopen("data.cpp","r",stdin);cin>>n>>m;for(int i=1;i<=m;i++){cin>>v[i];cnt[v[i]]++;}//每个数,还有每个数出现的次数 /*已知a<b<c<d,b-a=2(d-c), b-a<(c-b)/3设t=d-c于是b-a=2*t根据b-a<(c-b)/3,变成3*(b-a)<c-b,再变成6t<c-b,所以c-b=6t+k(变数k可以从1逐个变大,限制于极限n) 结论:数轴里1到极限n的线段中能找到a、b、c、d四个位置a( 间隔2t )b( 间隔6t+k(从1到整体限制的变数) )c( 间隔t )dt在极限n(9t+1<=n)范围内可大可小a从最大(n-9t-1)减小到1,每次都会有唯一对应的b=a+2t也有对应的c=b+6t+1(k最小=1时),和d=c+t但是,因着k的增加,同样的ab有很多cd,这个没有当时体现,在a减小过程中逐渐增多 所以在a变化过程中生成的cd数要累加(后缀和),不仅仅可以和现在的ab组合,还要和往后的ab组合这里算了ab出现的次数 d从最小(9t+2)增加到n,每次有唯一对应c=d-tk时1是对应的b=c-6t-1(k最小=1),a=b-2t同样随着d的增加会有不同的ab,这些其实也是每对cd因不同k所得,要累加这次循环只算了cd次数 */for(int t=1;9*t+1<=n;t++){//最大的数n起码能分成九分,还多个最小的k int a,b,c,d,//四个数sum=0;//前缀和,随着d的增加对应的ab逐渐增加(其实是同一对cd因k会有很多对应的ab) for(d=9*t+2;d<=n;d++){//随着d的增加k也增加。最小的d:a从1开始+9t+最小的k=9t+2。 c=d-t;//d生成唯一c a=d-9*t-1,b=a+2*t,//相对于d生成最小的a(a生成b),此时k=1,随着d的增加会得到不同k对应的ab(需要前缀和) sum+=cnt[a]*cnt[b];//几个a几个b形成多种组合。前缀和,要累加。跟后面的c、d(其实就是k不同)都可以组合 //a(2t)ba(2t)ba(2t)ba(2t)b……a(2t)b(6t+1)c(t)d,这个cd有很多对应的ab,d增加,前序会变多 C[c]+=sum*cnt[d],D[d]+=sum*cnt[c];//这里只算了c、d的,没有算a、b}sum=0;for(int a=n-9*t-1;a>=1;a--){//a数值要逐渐减小,怎样后序cd数量会增加。 b=a+2*t;//a生成唯一b c=b+6*t+1,d=c+t;//相对于b得到k=1最小时的c(c生成唯一d),随着a的增加可以得到很多k变化后的cd,前缀和 sum+=cnt[c]*cnt[d];//相对于变化的a(a生成唯一b)生成不同k的c、d,这些c、d要累加,作用到后面的a、b//a(2t)b(6t+1)c(t)d……c(t)dc(t)dc(t)dc(t)d,没对ab都有很多对应的cd,a值减小,后继会变多 A[a]+=sum*cnt[b],B[b]+=sum*cnt[a]; //这里只算了a、b的,没有重复算c、d}}//view();for(int i=1;i<=m;i++){cout<<A[v[i]]<<" "<<B[v[i]]<<" "<<C[v[i]]<<" "<<D[v[i]]<<endl;//每个数分别充当a、b、c、d的次数 }return 0;
}
