LCX的算法笔记Week-5
DP
建立一个有序容器map,键值为边的权值,值为该权值的所有边构成的vector
名为ans的map代表以某点为结尾的递增序列的所有方案
遍历map,即从小到大遍历所有边,每遍历一个vector,就会更新这个vector所存的边的端点的值
更新的时候,因为新加入一条边,方案数应先加1(区分该点是否在本次遍历中出现过)
再加上以另一点为结尾的方案数(即从x->y或y->x)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <bits/stdc++.h> #define f first #define s second using namespace std; typedef long long ll; typedef pair<int,int> pii; map<int,ll> ans; map<int, vector<pii>> road; int n; int main() { cin.tie(0); ios::sync_with_stdio(false); cin>>n; for (int i=1; i<n; i++) { int a,b,c; cin>>a>>b>>c; road[c].push_back({a,b}); } for(auto i:road) { map<int,ll> temp; for(auto j:i.s) { if (temp.count(j.f)) { temp[j.f]++; } else temp[j.f]=ans[j.f]+1; temp[j.f]+=ans[j.s]; if (temp.count(j.s)) { temp[j.s]++; } else temp[j.s]=ans[j.s]+1; temp[j.s]+=ans[j.f]; } for(auto j:temp) { ans[j.f]=j.s; } } ll sum=0; for(auto i:ans) { sum+=i.s; } cout<<sum; }
|
欧拉回路
每一个城市的名字即代表首字母到尾字母的一条有向边,问题即转换为判断该有向图是否存在欧拉回路
- 无向图存在欧拉回路的充要条件
一个无向图存在欧拉回路,当且仅当该图所有顶点度数都为偶数,且该图是连通图。
- 有向图存在欧拉回路的充要条件
一个有向图存在欧拉回路,所有顶点的入度等于出度且该图是连通图。
该图为有向图,所以先判断是否为连通图,采用并查集来判断,每加入一条边,就把两个顶点加入到一个集合,建一条由首字母到尾字母的有向边。最后判断是否所有点都在一个集合中,并且所有点的入度等于出度,所以对于每个点,当它为起始点就让degree++,否则–,最后判断是否为0即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <bits/stdc++.h> using namespace std; int deg[26],n; int fa[26],f; bool state[26]; int find(int x) { if (fa[x]!=x) { fa[x]=find(fa[x]); } return fa[x]; } void merge(int x,int y) { fa[find(x)]=find(y); } int main() { cin>>n; for (int i=0; i<=25; i++) { fa[i]=i; } while (n--) { string t; cin>>t; int len=(int)t.size(); int u=t[0]-'a',v=t[len-1]-'a'; deg[u]++; deg[v]--; state[u]=state[v]=true; merge(u,v); f=fa[u]; } bool flag=true; for (int i=0; i<=25; i++) { if ((deg[i]||find(i)!=f)&&state[i]) { flag=false; } } if (flag) { cout<<"YES"; } else cout<<"NO"; return 0; }
|
区间选点
给定 N 个闭区间 [ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。
输出选择的点的最小数量。
位于区间端点上的点也算作区间内。
将区间按右端点排序,每次选出右端点,遍历区间,如果该区间左端点大于当前维护右端点最大值,则把当前右端点加入集合并更新最大值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e6+10; int n; struct range{ int l,r; bool operator <(const range & t)const { return r<t.r; } } ran[N]; int main() { cin>>n; for (int i=0; i<n; i++) { cin>>ran[i].l>>ran[i].r; } sort(ran,ran+n); int rmax=-2e9; int ans=0; for (int i=0; i<n; i++) { if (ran[i].l>rmax) { ans++; rmax=ran[i].r; } } cout<<ans; return 0; }
|
最大不相交区间数量
给定 N 个闭区间 [ai,bi],请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。
输出可选取区间的最大数量。
同样地对右端点排序,做法和上题一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e6+10; int n; struct range{ int l,r; bool operator <(const range & t)const { return r<t.r; } } ran[N]; int main() { cin>>n; for (int i=0; i<n; i++) { cin>>ran[i].l>>ran[i].r; } sort(ran,ran+n); int rmax=-2e9; int ans=0; for (int i=0; i<n; i++) { if (ran[i].l>rmax) { ans++; rmax=ran[i].r; } } cout<<ans; return 0; }
|
区间分组
给定 N 个闭区间 [ai,bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。
输出最小组数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e6+10; int n; struct range{ int l,r; bool operator <(const range & t)const { return l<t.l; } } ran[N]; int main() { cin>>n; for (int i=0; i<n; i++) { cin>>ran[i].l>>ran[i].r; } sort(ran,ran+n); priority_queue<int,vector<int>,greater<int>> heap; for (int i=0; i<n; i++) { auto t=ran[i]; heap.push(t.r); if (heap.top()<t.l) { heap.pop(); } } cout<<heap.size(); return 0; }
|
另一个很巧妙的解法
转换为上课问题,有若干个活动,第i个活动开始时间和结束时间是[si,fi],同一个教室安排的活动之间不能交叠,求要安排所有活动,少需要几个教室?
有时间冲突的活动不能安排在同一间教室,与该问题的限制条件相同,即最小需要的教室个数即为该题答案。
我们可以把所有开始时间和结束时间排序,遇到开始时间就把需要的教室加1,遇到结束时间就把需要的教室减1,在一系列需要的教室个数变化的过程中,峰值就是多同时进行的活动数,也是我们至少需要的教室数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e6+10; int n; int a[N],idx; int main() { cin>>n; for (int i=0; i<n; i++) { int l,r; cin>>l>>r; a[idx++]=2*l; a[idx++]=2*r+1; } sort(a,a+idx); int ans=0,cur=0; for (int i=0; i<idx; i++) { ans=max(ans,cur); if (a[i]%2) { cur--; } else cur++; } cout<<ans; return 0; }
|
区间覆盖
给定 N 个闭区间 [ai,bi] 以及一个线段区间 [s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。
输出最少区间数,如果无法完全覆盖则输出 −1。
对区间按左端点排序,每次选一个左端点小于线段区间st且右端点最大的,并用此右端点更新st,当最大右端点小于st则代表不能完全填满,当最终结果区间st<ed同样代表区间不能被填满
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e6+10; int n; int st,ed; struct range{ int l,r; bool operator <(const range & t)const { return l<t.l; } } ran[N]; int main() { cin>>st>>ed; cin>>n; for (int i=0; i<n; i++) { cin>>ran[i].l>>ran[i].r; } sort(ran,ran+n); int res=0; for (int i=0; i<n; i++) { int j=i; int rmax=-2e9; while (j<n&&ran[j].l<=st) { rmax=max(rmax,ran[j].r); j++; } if (rmax<st) { res=-1; break; } res++; st=rmax; i=j-1; if (rmax>=ed) { break; } } if (st<ed) { res=-1; } cout<<res; return 0; }
|
合并果子(Huffman树)
在一个果园里,达达已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。
达达决定把所有的果子合成一堆。
每一次合并,达达可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。
可以看出,所有的果子经过 n−1 次合并之后,就只剩下一堆了。
达达在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以达达在合并果子时要尽可能地节省体力。
假定每个果子重量都为 1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使达达耗费的体力最少,并输出这个最小的体力耗费值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e6+10; int n,ans; priority_queue<int,vector<int>,greater<int>> heap; int main() { cin>>n; for (int i=0; i<n; i++) { int t; cin>>t; heap.push(t); } while (heap.size()>1) { int a1=heap.top(); heap.pop(); int a2=heap.top(); heap.pop(); ans+=a1+a2; heap.push(a1+a2); } cout<<ans; return 0; }
|
排队打水(排序不等式)
有 n 个人排队到 1 个水龙头处打水,第 i 个人装满水桶所需的时间是 ti,请问如何安排他们的打水顺序才能使所有人的等待时间之和最小?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e6+10; int n,a[N]; ll ans; int main() { cin>>n; for (int i=0; i<n; i++) { cin>>a[i]; } sort(a,a+n); for (int i=0; i<n; i++) { ans+=a[i]*(n-i-1); } cout<<ans; return 0; }
|
货仓选址(绝对值不等式)
在一条数轴上有 N 家商店,它们的坐标分别为 A1∼AN。
现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。
为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。
- 每两个点一组,只要选的点在这两个点之间,就能使该点到两点之和最小(为两点距离)所以选中位数即可求到最优解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e6+10; int n,a[N]; ll ans; int main() { cin>>n; for (int i=0; i<n; i++) { cin>>a[i]; } sort(a,a+n); for (int i=0; i<n; i++) { ans+=abs(a[i]-a[n/2]); } cout<<ans; return 0; }
|
耍杂技的牛
农民约翰的 N 头奶牛(编号为 1..N)计划逃跑并加入马戏团,为此它们决定练习表演杂技。
奶牛们不是非常有创意,只提出了一个杂技表演:
叠罗汉,表演时,奶牛们站在彼此的身上,形成一个高高的垂直堆叠。
奶牛们正在试图找到自己在这个堆叠中应该所处的位置顺序。
这 N 头奶牛中的每一头都有着自己的重量 Wi 以及自己的强壮程度 Si。
一头牛支撑不住的可能性取决于它头上所有牛的总重量(不包括它自己)减去它的身体强壮程度的值,现在称该数值为风险值,风险值越大,这只牛撑不住的可能性越高。
您的任务是确定奶牛的排序,使得所有奶牛的风险值中的最大值尽可能的小。
- 将每头奶牛的wi+si排序,从小到大的顺序即为奶牛从上到下的顺序,易证若非递增则交换后,最大值会变小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int, int> pii; const int N=1e5+10; int n; ll ans=-2e9,sum; pii cow[N]; int main() { cin>>n; for (int i=0; i<n; i++) { int w,s; cin>>w>>s; cow[i]={w+s,w}; } sort(cow,cow+n); for (int i=0; i<n; i++) { int s=cow[i].first-cow[i].second,w=cow[i].second; ans=max(ans,sum-s); sum+=w; } cout<<ans; return 0; }
|
表达式求值(中缀表达式)
给定一个表达式,其中运算符仅包含 +,-,*,/
(加 减 乘 整除),可能包含括号,请你求出表达式的最终值。
注意:
- 数据保证给定的表达式合法。
- 题目保证符号
-
只作为减号出现,不会作为负号出现,例如,-1+2
,(2+2)*(-(1+1)+2)
之类表达式均不会出现。
- 题目保证表达式中所有数字均为正整数。
- 题目保证表达式在中间计算过程以及结果中,均不超过 231−1。
- 题目中的整除是指向 0 取整,也就是说对于大于 0 的结果向下取整,例如 5/3=1,对于小于 0 的结果向上取整,例如 5/(1−4)=−1。
- C++和Java中的整除默认是向零取整;Python中的整除
//
默认向下取整,因此Python的eval()
函数中的整除也是向下取整,在本题中不能直接使用。
存两个栈,一个操作符栈,一个数字栈,当要入栈的操作符优先级小于等于栈顶的操作符,则用栈顶的操作符进行一次计算,当遇到左括号时,直接入栈,当遇到右括号时将一直到左括号的运算符全部进行运算,最后对剩下的运算符进行运算操作即可得到最终结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #include <bits/stdc++.h> using namespace std; unordered_map<char, int> dic{{'+',1},{'-',1},{'*',2},{'/',2}}; stack<char> op; stack<int> num; void calculate() { int b=num.top();num.pop(); int a=num.top();num.pop(); char o=op.top();op.pop(); int res=0; if (o=='+') { res=a+b; } if (o=='-') { res=a-b; } if (o=='*') { res=a*b; } if (o=='/') { res=a/b; } num.push(res); } int main() { string s; cin>>s; for (int i=0; i<s.size(); i++) { if (isdigit(s[i])) { int x=0,j=i; while (j<s.size()&&isdigit(s[j])) { x=x*10+s[j]-'0'; j++; } i=j-1; num.push(x); } else if (s[i]=='(') op.push(s[i]); else if (s[i]==')') { while (op.top()!='(') { calculate(); } op.pop(); } else { while (op.size()&&dic[op.top()]>=dic[s[i]]) { calculate(); } op.push(s[i]); } } while (op.size()) { calculate(); } cout<<num.top(); return 0; }
|
KMP字符串
给定一个模式串 S,以及一个模板串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模板串 P 在模式串 S 中多次作为子串出现。
求出模板串 P 在模式串 S 中所有出现的位置的起始下标。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e6+10; int n,m,ne[N]; char p[N],s[N]; int main() { cin>>n>>(p+1); cin>>m>>(s+1); for (int i=2,j=0; i<=n; i++) { while (j&&p[i]!=p[j+1]) { j=ne[j]; } if (p[i]==p[j+1]) { j++; } ne[i]=j; } for (int i=1,j=0; i<=m; i++) { while (j&&s[i]!=p[j+1]) { j=ne[j]; } if (s[i]==p[j+1]) { j++; } if (j==n) { cout<<i-n<<' '; j=ne[n]; } } return 0; }
|