2021-07-07上课总结
一、枚举
枚举,就是通过循环、数组等方式进行暴力查找寻找答案。这种方法很容易找到正解,但是时间复杂度会是O(n)以上,要考虑数据范围。
经典例题
[AtCoder ABC143D] Triangles
【问题描述】
小ZZ有NN条木棍,每条木棍的长度为DiDi。
他想在这NN条木棍中选择三条,让这三条木棍能构成一个三角形。小ZZ想知道有多少种方案能构成三角形(不同的方案定义两个方案中至少有一根木棍不相同)。
【输入格式】
输入第一行一个整数NN,表示木棍的个数。
第二行NN个整数DiDi,表示木棍的长度。
【输出格式】
输出一个整数,表示构成三角形的方案数。
分析
这是一道经典的枚举题。有心机的人会因为数据范围而不敢用三重循环,但实际上只需要加一个sort,一个特判,改一下循环开始的条件,三重循环是会压线的,不超时。
code环节
#include<bits/stdc++.h>
using namespace std;
int f[2010];
int main()
{
ios::sync_with_stdio(false);//加速用的东西,可以直接用scanf,不用这句话
int n;
cin>>n;
int ans=0;
for(int i=1;i<=n;i++) cin>>f[i];
sort(f+1,f+n+1);
for(int i=1;i<=n;i++)
for(int j=1+i;j<=n;j++)//因为要从里面挑选,所以循环开始一定是上一层循环+1
{
if(f[i]+f[j]<f[j+1]) continue;//因为sort了,所以f[j+1]是第一个比f[j]大的数。如果这句话成立,说明三角形两边和已经小于第三边了。
for(int k=1+j;k<=n;k++)
{
if(f[i]+f[j]>f[k] && f[i]+f[k]>f[j] && f[j]+f[k]>f[i])//正常判断
ans++;
}
}
cout<<ans;
return 0;
}
二、模拟
模拟,就是按照题目要求一步一步码代码,依照题目的细节、要求来打。这类题一般码量较大,细节较多,但是很适合操作类的的题目。
经典例题
[CSP-J2019] 公交换乘
著名旅游城市 B 市为了鼓励大家采用公共交通方式出行,推出了一种地铁换乘公交车的优惠方案:
在搭乘一次地铁后可以获得一张优惠票,有效期为 45 分钟,在有效期内可以消耗这张优惠票,免费搭乘一次票价不超过地铁票价的公交车。在有效期内指开始乘公交车的时间与开始乘地铁的时间之差小于等于 45 分钟,即:
tbus−tsubway≤45tbus−tsubway≤45
搭乘地铁获得的优惠票可以累积,即可以连续搭乘若干次地铁后再连续使用优惠票搭乘公交车。
搭乘公交车时,如果可以使用优惠票一定会使用优惠票;如果有多张优惠票满足条件,则优先消耗获得最早的优惠票。
现在你得到了小轩最近的公共交通出行记录,你能帮他算算他的花费吗?
输入格式
输入文件的第一行包含一个正整数 nn,代表乘车记录的数量。
接下来的 nn 行,每行包含 3 个整数,相邻两数之间以一个空格分隔。第 ii 行的第 1 个整数代表第 ii 条记录乘坐的交通工具,0 代表地铁,1 代表公交车;第 2 个整数代表第 ii 条记录乘车的票价 priceipricei ;第三个整数代表第 ii 条记录开始乘车的时间 titi(距 0 时刻的分钟数)。
我们保证出行记录是按照开始乘车的时间顺序给出的,且不会有两次乘车记录出现在同一分钟。
输出格式
输出文件有一行,包含一个正整数,代表小轩出行的总花费。
分析
这道%你题,一般人的思路应该是开结构题记录优惠券的时间、钱数、状态,随后用双重循环去枚举优惠券。但是这样很容易超时。优化的方法是在二重循环上动手脚,改变一下循环开始的因素。
code环节
#include<bits/stdc++.h>
using namespace std;
const int mmm=100010;
struct bybus
{
long long timee;
int pricee;
bool heer;
}quan[mmm];
int main()
{
ios::sync_with_stdio(false);
int n,q=0,p=1;
cin>>n;
long long payy=0;
for(int i=1;i<=n;i++)
{
int wayy,price,time;
cin>>wayy>>price>>time;
if(wayy==0)//是地铁,没有优惠券,但一定会成为优惠券
{
payy+=price;//没券,直接加钱
q+=1;//记录券的数量
quan[q].pricee=price;
quan[q].heer=1;//券可否使用
quan[q].timee=time;
//因为一定会成为券,所以直接记录内容
}
else
{
bool flag=0;//记录用券了没
for(int j=p;j<=q;j++)
{
if(time-quan[j].timee>45) p=j;//相当于j++
if(quan[j].heer==1 && quan[j].pricee>=price && time-quan[j].timee<=45)//判断条件
{
quan[j].heer=0;
flag=1;
break;//因为这里直接退出循环了,所以上面要用一种特殊的方法来实现j++
}
}
if(flag==0)
payy+=price;//没用券,加钱
}
}
cout<<payy;
return 0;
}
三、前缀和、差分
前缀和和差分都是预处理,前缀和可以让求区间和值的时间复杂度降到O(1);
差分可以让区间整体加(减)一个数的时间复杂度降到O(1)。前缀和和差分是互逆运算。在这里就不放前缀和和差分的例题了,希望好好理解一下,拿几道水题练练手。