# 暴力出奇迹!!!!!
第二次做这个题 感受颇深
何出此言,对于我好久以前做此题,没有任何思路,除了30的暴力不加剪枝,
然后我就看了博主一的题解,感觉很懂的样子,
时至今日,忘光光了,然后就有了暴力出奇迹!
进入正题
对于本题,一开始想到了两种方法。
第一种,暴力枚举同样色调的客栈,然后枚举其中的满足最低消费的客栈,显然会TLE。而且死的很惨!!代码就很简单了
第二种,暴力枚举满足最低消费的客栈,然后预处理每个客栈每种两边客栈颜色的数量,然后ans直接加左右相乘就ok,然而码到一半的时候,果断放弃,为什么? 两个客栈之间会有多个满足最低消费的客栈。
正解
结合一下一二种方法(也不算是结合,优化一下第一种的做法)。我们枚举一个客栈,然后找到在它之后的一个满足最低消费的客栈。预处理此满足最低消费客栈之后(包括此客栈)和枚举的那个客栈的颜色相同的客栈。(细细体会,不难理解)
上代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
const int N=200050,K=55,P=150;
int n,k,p,col[N],w[N],r[N][K],wz[N],num=0,fl=0;
ll ans=0;
int find(int x){//二分查找离x客栈最近的满足最低消费的客栈,个人觉得二分会快点
int a=1,b=num,ddd=-999;
while(a<=b){
int mid=(a+b)/2;
if(wz[mid]>=x){
ddd=wz[mid];
b=mid-1;
}
else a=mid+1;
}
return ddd;
}
int main(){
scanf("%d%d%d",&n,&k,&p);
for(int i=1;i<=n;i++){
scanf("%d%d",&col[i],&w[i]);
if(w[i]<=p){
num++;
wz[num]=i;//统计满足最低消费的位置
}
}
for(int i=n;i>=1;i--){
for(int j=0;j<k;j++)
if(col[i]==j)
r[i][j]=r[i+1][j]+1;
else
r[i][j]=r[i+1][j];
} //预处理每个节点之后的各种色调的客栈数量
for(int i=1;i<=n;i++){
if(fl<i) fl=find(i);//只有当前的在最近的满足最低消费的客栈之后,再更新最近客栈,毕竟可以节约时间
if(fl!=-999){//之后没有满足的客栈了,直接跳出循环
if(fl==i) //这个地方是个坑点,因为两个人不能住同一个客栈
ans+=r[fl][col[i]]-1;//因为我们统计的时候算上了本身,所以要减去一
else ans+=r[fl][col[i]];
}
else break;
}
printf("%d",ans);
return 0;
}
然而,这题有更优的做法(此话怎讲?)
O(N),直接线性推。用三个数组维护,sum[col],cnt[col],last[col]。也就算是上面代码的更优化。
sum[col]把统计后面的满足最低消费客栈的数量(K*N)改为统计前面的(后面也一样)。
cnt[col]记录前面的col颜色的数量。
last[col]相当于二分枚举合法位置。。。。。。。。。。。。。。。。。。。。。。。
(题解就是不一样(补营养),orz orz)
废话少说,上代码!
#include<iostream>
#include<cstdio>
using namespace std;
const int K=55;
int n,k,p,cnt[K],last[K],now,ans,sum[K];
int main(){
scanf("%d%d%d",&n,&k,&p);//输入数据
for(int i=1;i<=n;i++){
int col,w;
scanf("%d%d",&col,&w);
if(w<=p) now=i;//更新合法位置
if(last[col]<=now) sum[col]=cnt[col];//求满足要求的客栈数
last[col]=i;//更新上一个col的位置
ans+=sum[col];//更新答案
cnt[col]++;//col数量增加
}
printf("%d",ans);//输出答案
return 0;
}
本题在于优化的思路,不在于题的难易。思路可以应用在各大难题上。(该暴力的还是暴力,想不出正解orz)
谨记此博文记于2019,3,21(orz,orz)