1🐋🐋🐋门票(钻石;前缀和)
时间限制:1秒
占用内存:128M
🐟题目描述
🐟输入输出格式
🐟样例
🐚样例
🐚备注
🐟题目思路
这道题目乍看就是简单的前缀和+判断区间值而已,但是暴力求解只能拿70分。满分题解是求逆序数+归并排序。至于为什么求逆序数,那是因为,如果该段内都满足条件,那前缀和的值应该是非减的,所以所有可能减去逆序数量就是我们要的答案了。
🌮归并排序知识补充
定义
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
算法思路
归并排序算法有两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程。另一个是治,它将两个有序数组合并成一个更大的有序数组。
-
1、将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。
-
2、将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。
分治图解
排序图解
算法性能
速度仅次于快速排序。
时间复杂度
O(nlogn)。
空间复杂度
O(N),归并排序需要一个与原数组相同长度的数组做辅助来排序。
稳定性
稳定。
摘自:排序——归并排序(Merge sort)-CSDN博客
🐟代码
🥪70分暴力求解(超时)
#include<bits/stdc++.h>
using namespace std;
int a[1000010],sum[1000010];
const int N=1e9+7;
int main( )
{int n,t;cin>>n>>t;long long count=0;for(int i=0;i<n;i++) cin>>a[i];for(int i=0;i<n;i++) sum[i]=sum[i-1]+a[i];for(int i=0;i<n;i++){for(int j=i;j<n;j++){int cn=j-i+1;if((sum[j]-sum[i-1])/cn>=t) count++;}}count=count%N;cout<<count<<endl;return 0;
}
🥪100分逆序数+归并排序
#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
const int N=1e6+10;
#define ll long long
ll n,t,a[N],sum[N],ans;//ans记录逆序子序列数量
ll q[N];//归并排序的结果数组
void merge_sort(int l,int r)
{if(l>=r) return;//递归停止条件//分int mid=(l+r)/2;merge_sort(l,mid);//划分成子任务并分别递归调用归并排序算法merge_sort(mid+1,r);//归并算法本体int i=l,j=mid+1,t=0;//i和j分别是左右两部分的指针,t是结果数组当前要放位置的下标while(i<=mid&&j<=r){if(sum[i]>sum[j])//结果数组先放小的那个,也就是j那里的{q[t++]=sum[j++];ans+=mid-i+1;//对于每一部分来说,前边的分治任务都已经将这一部分排好序了,所以i~mid和j~r这两部分都已经是升序排列,如果i那里的比j这里的大,那么i+1~mid的一定比j这里的大,所以求逆序数直接减就可以了ans%=MOD;}else q[t++]=sum[i++];//否则就放i那里的,这时不计逆序数}//这时i或j某一边提前结束的情况,另一边直接全部移到结果数组while(i<=mid) q[t++]=sum[i++];while(j<=r) q[t++]=sum[j++];for(i=l,j=0;i<=r;i++,j++) sum[i]=q[j];//最后更新一下sum数组,因为每次都是基于sum计算的
}
int main( )
{cin>>n>>t;long long count=0;for(int i=1;i<=n;i++){cin>>a[i];a[i]-=t;//根据公式,平均值大于等于t,也就是求和大于等于nt,所以每次直接在a[i]中就减去t,这样只要区间和大于等于0就满足条件sum[i]=sum[i-1]+a[i];} merge_sort(0,n);//归并排序cout<<(n*(n+1)/2-ans)%MOD<<endl;//所有可能子序列数减去ans数量并取模return 0;
}
2🐋🐋🐋逆波兰式(星耀;栈)
时间限制:1秒
占用内存:128M
🐟题目描述
🐟输入输出格式
🐟样例
🐚样例
🐚备注
🐟题目思路
这道题目还是用栈进行算式运算,只用两个栈就可以实现结果输出那种样子,一个是st存放结果字符串成员,一个是op临时存放操作符。数字直接放入st,操作符在遇到以下情况时要从op拿出放到st:
-
当前为“)”,需要将op中的小括号前的所有操作符放入st,最后别忘了把“(”也pop掉
-
当前运算符比op.top( )的优先级低或同级,要将op.top( )的放入st,目的是保证运算顺序,包括同级从左到右运算的顺序
到这里第一步就结束了,接下来就是正常的calculate函数运算了,多的就是遇到运算符时进行运算后需要cout出来当前运算结果情况
🐟代码
#include<bits/stdc++.h>
#include<string>
using namespace std;
bool is_op(char c){ return c =='/'||c=='+'||c=='-'||c=='*';}
int priority(char op){if(op == '+'|| op == '-') return 1;if(op == '*' || op == '/') return 2;return -1;
}
void calculate(string s){stack<int> st;bool change = false;for(int i=0;i<s.length(); i++){if(is_op(s[i])){int r= st.top();st.pop();int l= st.top();st.pop();switch(s[i]){//这里不知道为什么改成if语句就报错case '+':st.push(l + r);break;case '-':st.push(l-r);break;case '*':st.push(l * r);break;case '/':st.push(l /r);break;}change = true;} elsest.push(s[i]-'0'),change = false;if(change){stack<int> temp, temp2;temp = st;//反转顺序到temp2中while (!temp.empty())temp2.push(temp.top()), temp.pop();//对temp2中的成员进行输出while (!temp2.empty())cout << temp2.top()<<" ",temp2.pop();for(int j=i+1;j<s.length(); j++)cout << s[j] << " ";cout << endl;}}
}
string work(string s){ stack<char> st;//结果stack<char> op;//操作符栈for(int i=0;i<s.size();i++){if(s[i]=='('){op.push('(');}else if(s[i]==')'){while (op.top()!='('){st.push(op.top());op.pop();}op.pop();}else if(is_op(s[i])){while(!op.empty() && priority(op.top())>= priority(s[i])){st.push(op.top());op.pop();}op.push(s[i]);} else {st.push(s[i]);}}while(!op.empty()){st.push(op.top());op.pop();}string ans;int len = st.size();while(len--){ans =st.top()+ ans;st.pop();}return ans;
}
int main( )
{string s;cin >> s;string s2=work(s);for(int i=0;i<s2.length(); i++)cout << s2[i] <<" ";cout << endl;calculate(s2);return 0;
}
3🐋🐋🐋新月轩就餐(钻石;双端队列)
时间限制:1秒
占用内存:128M
🐟题目描述
🐟输入输出格式
🐟样例
🐚样例
🐚备注
🐟题目思路
这道题目因为涉及到顺序不变问题,所以使用deque双端队列,目的是方便改变区间的首尾。
具体详见代码注释~
🌮deque vs priority_queue
这里主要对比的是双端队列deque和最大/小堆priority_queue,二者有以下不同:
-
端口进出:deque双端都可进出;priorty_queue只有一端可进出
-
排序:deque中的元素无法排序,只是queue加上一端可操作而已;priority_queue中的元素是要进行排序的,每次top的是最大/小值
-
使用的函数:deque用的是front和back;priority_queue用的是top(这一点只是提醒一下,不要用错了)
🐟代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+7;
const int INF=0x3f3f3f;
deque<int> q;
int ans=INF;
int n,m,a[N],cnt[2001],l,r,type;
int main( )
{cin>>n>>m;for(int i=1;i<=n;i++){cin>>a[i];if(!cnt[a[i]]) type++;//该类型还没有记录过,记一下cnt[a[i]]++;//标记为记录过了q.push_back(i);//当前位置入队列while(!q.empty()&&cnt[a[q.front()]]>1) //这是在判断,如果头上的元素在当前区间内还有,那么头上这个就可以去掉了,这样就能得到更小的区间了{cnt[a[q.front()]]--;q.pop_front();}if(type==m&&q.size()<ans) //所有类型都有了,并且比当前记录的最短区间长度还要短,就更新{ans=q.size();l=q.front();r=q.back();}}cout<<l<<" "<<r<<endl;return 0;
}
4🐋🐋🐋植发(星耀;堆)
时间限制:2秒
占用内存64M
🐟题目描述
🐟输入输出格式
🐟样例
🐟题目思路
这个重点在于怎么安排头发的放置顺序和放在哪里:
-
先放寿命最小的点,因为寿命小的点所能到达的位置,寿命大的都能到达
-
先放到离出发点最近的位置,这样可以给另一个出发点的头发更多的放置机会
🐟代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
struct node
{int x,y;int dis;bool operator<(const node &a) const {return dis<a.dis;}
};
int a1[N],a2[N],n,m,k,l;//a1a2存储了不同寿命的头发都有多少根
bool used[N][N];
vector<pair<int,int> > s1[N],s2[N];//s1s2存储的是不同距离下有哪些点,s1是与(0,0)的距离,s2是与(0,m+1)的距离
priority_queue<node> down,up;
int distance(int x1,int y1,int x2,int y2) {return abs(x1-x2)+abs(y1-y2);}
int main( )
{cin>>n>>m;cin>>k;//获得(0,0)处的头发寿命for(int i=0;i<k;i++){int cur;cin>>cur;a1[cur]++;//(0,0)处寿命为cur的头发数加一}cin>>l;//获得(0,m+1)处的头发寿命for(int i=0;i<l;i++){int cur;cin>>cur;a2[cur]++;//(0,m+1)处寿命为cur的头发数加一}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)//记录每个距离都有哪些位置{s1[distance(i,j,0,0)].push_back({i,j});//记录与(0,0)的距离s2[distance(i,j,0,m+1)].push_back({i,j});//记录与(0,m+1)的距离}//如果有多个点和(0,0)或(0,m+1)距离相同,就会被都放进去}int flag=0;//先看与(0,0)的距离for(int i=1;i<=n+m;i++)//遍历所有n+m个距离{for(int j=0;j<s1[i].size();j++)//对于指定的距离,遍历满足这些距离的所有点的坐标,把符合的点放到队列里{int x=s1[i][j].first,y=s1[i][j].second;int tmp=distance(x,y,0,m+1);down.push({x,y,tmp});//因为使用的是最大堆,要想距离(0,0)最近,就是按距离(0,m+1)的距离排序得到与之的最大距离}for(int j=0;j<a1[i];j++)//来放(0,0)的每一根头发,遍历距离为i的所有头发{if(down.empty())//从(0,0)出发距离为i处没有能放的头发了{flag=1;break;}int tmpx=down.top().x,tmpy=down.top().y;//得到在(0,0)处拿到的与(0,m+1)距离最大的坐标down.pop();used[tmpx][tmpy]=1;//这个位置被放上头发了}if(flag==1) break;}//再看离(0,m+1)的距离,下边与上方同理for(int i=1;i<=n+m;i++){for(int j=0;j<s2[i].size();j++){int x=s2[i][j].first,y=s2[i][j].second;if(used[x][y]==1) continue;node tmp;tmp.dis=distance(x,y,0,0),tmp.x=x,tmp.y=y;up.push(tmp);}for(int j=0;j<a2[i];j++){if(up.empty()){flag=1;break;}int tmpx=up.top().x,tmpy=up.top().y;up.pop();used[tmpx][tmpy]=1;}if(flag==1) break;}if(flag==0) cout<<"YES"<<endl;else cout<<"NO"<<endl;return 0;
}
5🐋🐋🐋第一节离数课后(星耀;栈)
时间限制:0.5秒
占用内存:64M
🐟题目描述
🐟输入输出格式
🐟样例
🐚样例
🐚备注
🐟题目思路
这道题目也是使用栈进行表达式运算。重点是检查出有无非法表达的情况。
非法表达情况:
-
开头或结尾不能是and或not:以为她们是双目运算符,不能只有一个成员参与运算
-
not后边只能跟not或操作数
-
我们将not及其后边的操作数看作一个整体,那么操作数和运算符应该是一一间隔的,如 a and b or c
🐟代码
#include <bits/stdc++.h>
using namespace std;
const int N = 267;
string s[N];
int cnt, s2[N];
bool is_op(string s) {return s=="or"||s=="and"||s=="not";}
int priority(string op)
{if(op=="or") return 1;if(op=="and") return 2;if(op=="not") return 3;return -1;
}
void calculate(stack<string> &st, string op)
{//取出两个操作数(没有两个就取一个)string l, r;r = st.top();st.pop();if (!st.empty()) l = st.top();//计算if (op == "not"){if (r == "true") st.push("false");//not true即为falseelse st.push("true");//not false即为true}if (op == "and"){st.pop();if (r == "true" && l == "true") st.push("true");else st.push("false");}if (op == "or"){st.pop();if (r == "false" && l == "false") st.push("false");else st.push("true");}
}
string work(string *s, int cnt)
{stack<string> st;//操作数栈stack<string> op;//运算符栈for (int i = 0; i < cnt; i++){if (is_op(s[i])){string cur = s[i];if (cur != "not")while (!op.empty() && priority(op.top()) >= priority(cur)){calculate(st, op.top());op.pop();}op.push(cur);//先把优先级高的计算完再放进去这个运算符}else//是操作数{st.push(s[i]);while (!op.empty() && op.top() == "not"){calculate(st, op.top());op.pop();}}}while (!op.empty()){calculate(st, op.top());op.pop();}return st.top();
}
bool check(string *s, int cnt)//检查是否error
{bool flag = true;if (s[0]=="or"||s[0]=="and"||s[cnt-1]=="or"||s[cnt-1]=="and"||s[cnt-1]=="not")//1首尾为双目运算符;2尾为not(即not后边没有跟操作数)。这些都是error的情况{flag = false;return flag;}for (int i = 0; i < cnt; i++)//剩下需要遍历所有的内容检查not的正确性{if (s[i] == "not" && (s[i + 1] == "or" || s[i + 1] == "and"))//not后边紧跟运算符{flag = false;return flag;}}int cnt2 = 0;for (int i = 0; i < cnt; i++){if (s[i] == "or" || s[i] == "and")s2[cnt2++] = 1;else if (s[i] == "true" || s[i] == "false")s2[cnt2++] = 0;if (cnt2 >= 2 && (s2[cnt2 - 2] ^ 1) != s2[cnt2 - 1])//说明有两个运算符或操作数连在一起,如果是1010/0101,那么等式会相等{flag = false;return flag;}}return flag;
}
int main()
{while(cin >>s[cnt]) cnt++;if (!check(s,cnt)){cout << "error";return 0;}cout << work(s, cnt);return 0;
}
6🐋🐋🐋二阶前缀和(黄金;前缀和)
时间限制:1秒
占用内存:128M
🐟题目描述
🐟输入输出格式
🐟样例
🐚样例
🐚备注
🐟题目思路
二维矩阵前缀和的典型模板题,题目不难,只要看懂要干什么就好写很多,总结一下题目要点:
-
整个过程类似卷积的过程,就是拿着一个边长为r的正方形覆盖过整个矩形的所有位置,每次移动的距离为1
具体解题过程详见代码注释~
🐟代码
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int a[N][N], sum[N][N], ans;
int main( )
{// 录入数组内容的同时构建二维前缀和int n,r;cin>>n>>r;while(n--){int x,y,w;cin>>x>>y>>w;x++,y++;a[x][y] += w;}// 计算二维前缀和for(int i=1;i<=1001;i++){for(int j=1;j<=1001;j++) sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];} // 寻找指定范围的最大子阵,每次都是以(i,j)为右下角画正方形for(int i=r;i<=1001;i++){for(int j=r;j<=1001;j++) ans=max(ans,sum[i][j]-sum[i-r][j]-sum[i][j-r]+sum[i-r][j-r]); }// 输出结果 cout<<ans<<endl;return 0;
}
有问题我们随时评论区见~
⭐点赞收藏不迷路~