在透视这道题之前,先用一道小题来练练手
密码锁(这道题是我根据魔法阵改的)
Description
叮当在探索迷宫时被一扇大门挡住了去路,门上刻画了很多乱七八糟的数字,门被锁上了,无法打开,但是门上给出了提示:
1:正确按下门上的三个数字即可打开大门;
2:这三个数必须满足递增排列按下;
3:后两个数之差比前两个数之差的8倍还要大。
现在叮当想知道一共有多少种可能的情况来确定开门时间(对于门上不同位置的相同数字算不同的情况)。
Format
Input
第一行一个正整数n表示门上的数字(3<=n<=10000)。
第二行为n个正整数,表示每个数的值x(1<=x<=10000)。
Output
密码的可能情况。
Samples
Sample Input 1
11
2 2 3 3 4 4 4 13 13 13 13
Sample Output 1
40
Limitation
(2,3,13)共16种
(3,4,13)共24种
一共40种可能情况
对于30%的数据,3<=n<=500,1<=x<=500;
对于60%的数据,3<=n<=1500,x<=1500;
对于100%的数据,3<=n<=10000,1<=x<=10000;
思路
该题的本质就是选择三个数a,b,c,要求满足:
a<b<c,并且8*(b-a)<c-b很容易想到依次枚举a,b,c,由于是从小到大,我们可以把数组排序后,依次枚举,然后判断是否满足上面条件,时间复杂度为O(n^3)。
由于我们枚举的数可能不满足第二个条件,即枚举了很多无效的答案,所以效率很低,那么我们是 否可以保证我们在枚举的时候a,b,c, 一定就满足两个条件呢?
我们可以枚举a,然后枚举t=b-a,那么得到b的值 为a+t,可以计算出c的最小值为a+9*t+1。
如果按照这种方法枚举,那么我们的枚举方法就 不再是依次枚举每一个数了,因为我们没有办法直 接知道a+9*t+1到底是哪一个数。 那么我们可以换一种方法,枚举值,数的范围是 (1,10000),那么我们可以直接枚举1-10000,每个数 不止一种,先统计每种数的数量,再用到乘法原理 ,把a,b,c三个数的数量相乘。 在枚举的时候注意a,b,c的范围,尽量保证没有无 效的枚举,为了方便,其实我们可以先枚举t=b-a, 再枚举a,这样t的范围是[1,9*t+2<=m],a的范围是 [1,m-9*t-1],可以再枚举大的值k,k的范围是 [1,a+9*t+k<=m],而c=a+9*t+k。
这种枚举的方法,保证了枚举的所有数都是满足 题目给定条件的,时间复杂度相对于暴力枚举a,b,c 提高了81倍左右。
但是上述做法仍然不是该题的正解,该题的正解需要 用到继承种的单调性。 比如3 4 100满足条件,那么2 3 100,1 3 100是 不是仍然满足条件,也就是说,对于较大的a和b, 存在x个满足条件的c,那么对于比当前a,b小的情况 ,得到满足条件的c的个数y,那么实际上总的满足 条件的情况c一共有x+y个。 所以我们可以从大到小枚举a,累加当前情况下的 c的数量,当下一次枚举a的时候,答案为当前答案 加上前面的累加和,这样我们就没有必要再去枚举大于这个条件了,省去了一层循环。
ACcode
#include<bits/stdc++.h>
using namespace std;
int num[10100],maxn=INT_MIN;
int main(){
int n,tmp;
cin>>n;
for (int i=0;i<n;i++){
cin>>tmp;
num[tmp]++;
maxn=max(maxn,tmp);
}
long long cnt=0;
for (int t=1;9*t+2<=maxn;t++){
int s=0;
for (int a=maxn-9*t-1;a>=1;a--){
int b=a+t,c=b+8*t+1;
s+=num[c];
cnt+=num[a]*num[b]*s;
}
}
cout<<cnt;
return 0;
}
这时候就有人问了,密码锁跟魔法阵有什么关系呢?
别慌,我们先把魔法阵的题目看了。
题目背景
NOIP2016 普及组 T4
题目描述
六十年一次的魔法战争就要开始了,大魔法师准备从附近的魔法场中汲取魔法能量。
大魔法师有 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≤n≤15000,1≤m≤40000,1≤Xi≤n。每个 Xi 是分别在合法范围内等概率随机生成的。
输出格式
共 m 行,每行 4 个整数。第 i 行的 4 个整数依次表示编号为 i 的物品作 为 A,B,C,D 物品分别出现的次数。
保证标准输出中的每个数都不会超过 10^9。每行相邻的两个数之间用恰好一个空格隔开。
样例 #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
提示
【样例解释 1】
共有 5 个魔法阵,分别为:
物品 1,3,7,6,其魔法值分别为 1,7,26,29
物品 1,5,2,7,其魔法值分别为 1,5,24,26
物品 1,5,7,4,其魔法值分别为 1,5,26,28
物品 1,5,8,7,其魔法值分别为 1,5,24,26
物品 5,3,4,6,其魔法值分别为 5,7,28,29
以物品 5 为例,它作为 A 物品出现了 1 次,作为 B 物品出现了 3 次,没有作为 C 物品或者 D 物品出现,所以这一行输出的四个数依次为 1,3,0,0。
此外,如果我们将输出看作一个 m 行 4 列的矩阵,那么每一列上的 m 个数之和都应等于魔法阵的总数。所以,如果你的输出不满足这个性质,那么这个输出一定不正确。你可以通过这个性质在一定程度上检查你的输出的正确性。
【数据规模】
读完题后你看哈,密码锁是要求按下三个数字,且有两个要求,即枚举三个数字,而魔法阵是要求四个魔法物品的值满足三个要求,是不是差不多的呢,
先魔改一下这三个要求
设t=xd−xc
所以②可化为xb−xa=2t ④
将④代入③
2t<(xc−xb)/3
移项一下,就变成
6t<xc−xb ⑤
再魔改一下
设6t+k=xc−xb(就是把差的部分补上去)
所以就有了这个图:
既然有了这个图,那是不是就可以按照密码锁的套路来枚举了呢?只不过是要先枚举t,再在t的循环内正着枚举D和倒着枚举A就可以了。
首先由上图易得t的范围是1<=t<=(n-1)/9,并且还可以得到C=D-t。因为让A,B,C,D满足条件的最小的k是1,所以就有A=D-9t-1,B=D-7t-1;
其次就要看枚举D和枚举A的范围,首先是D:由上图可知D的max是n,而最小值则是当k=1且A=1的时候,所以D的最小值为9∗t+2,再小是无法组成魔法阵的。然后是A:A的最小值显然是1,最大值则是在D取到最大值,也就是n的时候,所以A的最大值就是n-9t-1。
范围知道了,那就直接求不就完了?所以这道题是非常非常简单的啦
ACcode
#include<bits/stdc++.h>
#define LL long long
using namespace std;
LL sum[41000],maxn=INT_MIN;
LL cmp[41000];
LL a[15100],b[15100],c[15100],d[15100];
int main(){
int m,n;
cin>>m>>n;
for (int i=0;i<n;i++){
cin>>cmp[i];
sum[cmp[i]]++;
maxn=max(maxn,cmp[i]);
}
for (int t=1;9*t+1<=m;t++){
LL s=0;
for (int d1=9*t+2;d1<=m;d1++){
int c1=d1-t,b1=c1-6*t-1,a1=b1-2*t;
s+=sum[a1]*sum[b1];
d[d1]+=s*sum[c1];
c[c1]+=s*sum[d1];
}//
s=0;
for (int a1=m-9*t-1;a1>=1;a1--){
int b1=a1+2*t,c1=b1+6*t+1,d1=c1+t;
s+=sum[d1]*sum[c1];
a[a1]+=s*sum[b1];
b[b1]+=s*sum[a1];
}//
}
for (int i=0;i<n;i++){
cout<<a[cmp[i]]<<" "<<b[cmp[i]]<<" "<<c[cmp[i]]<<" "<<d[cmp[i]]<<endl;
}
return 0;
}
看了这么久,作者也写了这么久,能不能点一个赞,在收藏一下呢?最好的话在点个关注吧
谢谢啦!