这里写自定义目录标题
第七章 贪心法课后作业
1.价值最大
题面
一种方法
可以切割物品,那么按照 b a \frac{b}{a} ab排序,从大到小贪心即可,这里使用 p a i r < e l e a , e l e b > pair<elea,eleb> pair<elea,eleb>这个可以存两个元素的数据结构。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
vector< pair<double, pair<ll, ll> > > num;
/*
struct pair{
elementa a;
elementb b;
};
*/
int main(int argc, char const *argv[])
{
// freopen("in.txt","r",stdin);
ll n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
int a,b;
cin>>a>>b;
num.push_back(make_pair(b*1.0/a, make_pair(a,b) ));
}
sort(num.begin(), num.end());
// for(auto tem:num){
// cout<<tem.first<<" "<<tem.second.first<<" "<<tem.second.second<<endl;
// }
double ans=0;
for(int i=num.size()-1;i>=0 && m;i--){
if(num[i].second.first<=m){
m-=num[i].second.first;
ans+=num[i].second.second;
} else {
ans+=num[i].first*m;
m=0;
}
// cout<<num[i].second.first<<" "<<num[i].second.second<<" "<<m<<" "<<ans<<endl;
}
printf("%.2f\n",ans);
return 0;
}
2.最小生成树
题面
一种方法
Kruskal算法,‘主角’是边,
- 首先将边按权重排序,升序,(Line 37)
- 从小到大遍历边 e < u , v > e<u,v> e<u,v>,(Line 41)
- 如果 u u u与 v v v不在同一个集合,则把边 e e e加入最小生成树,并且把 u u u所在的集合加入 v v v所在的集合(Line 45)
- 跳过 e e e
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e3 + 5;
vector<pair<int, pair<int, int> > >edge;
int fa[maxn];
int Find(int u) {
if (fa[u] != u) {
fa[u] = Find(fa[u]);
}
return fa[u];
}
void Merge(int u, int v) {
int fau = Find(u);
int fav = Find(v);
fa[fau] = fav;
}
int main(int argc, char const *argv[]) {
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int a, b, c;
cin >> a >> b >> c;
edge.push_back(make_pair(c, make_pair(a, b)));
}
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
sort(edge.begin(), edge.end());
int sum = 0;
int cnt = 0;
for (int i = 0; i < (int)edge.size(); i++) {
int u = edge[i].second.first;
int v = edge[i].second.second;
int w = edge[i].first;
if (Find(u) != Find(v)) {
sum += w;
Merge(u, v);
cnt++;
}
}
if (cnt == n - 1) {
cout << sum << endl;
} else {
cout << "NO\n";
}
return 0;
}
3.路在前方
题面
一种方法
最短路径,注意坑
- 有向边
- 可能存在重边
- 优先队列默认是大根堆,即队首元素最大
(太久没写了,忘记了,卡了我两小时。。。)
这里采用 D i j k s t r a Dijkstra Dijkstra算法,
- 起点入队,队首的点表示,离起点最近
- 取队首节点
u
,如果u
之前被访问过了,跳过,否则遍历u
的临点v
,v
到起点的距离为dist[v]
,v
可以通过u
再到起点,则距离为dist[u]+wuv(u->v的距离)
,如果dist[v]>dist[u]+wuv
,则v可以选择通过u到起点。
代码
/*
edge is directed
exist double or more e<a,b>
the top of priority_queue is the greatest as default
*/
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
vector<pair<int, int> > edge[maxn];
typedef long long ll;
ll dist[maxn];
int flag[maxn];
vector<pair<pair<int, int> , int > > temp;
int main(int argc, char const *argv[]) {
int n, m;
cin >> n >> m;
int x, z;
cin >> x >> z;
for (int i = 0; i <= n; i++) {
dist[i] = 0x7fffffffffffffff;
}
// cout<<dist[0];
dist[x] = 0;
for (int i = 1; i <= m; i++) {
int a, b, c;
cin >> a >> b >> c;
temp.push_back(make_pair(make_pair(a, b), c));
}
sort(temp.begin(), temp.end());
for (int i = 0; i < temp.size(); i++) {
int a, b, c;
a = temp[i].first.first;
b = temp[i].first.second;
c = temp[i].second;
if (i && a == temp[i - 1].first.first && b == temp[i - 1].first.second) {
continue;
}
edge[a].push_back(make_pair(b, c));
}
priority_queue<pair<ll, int>, vector<pair<ll, int> >, greater<pair<ll, int> > > q;
q.push(make_pair(0, x));
while (!q.empty()) {
int u = q.top().second;
q.pop();
if (flag[u]) continue;
flag[u] = 1;
for (int i = 0; i < (int)edge[u].size(); i++) {
int v = edge[u][i].first;
if (flag[v]) continue;
ll wuv = edge[u][i].second;
if (dist[v] > dist[u] + wuv) {
dist[v] = dist[u] + wuv;
q.push(make_pair(dist[v], v));
}
}
}
cout << dist[z] << endl;
return 0;
}
4.±1
题面
一种方法
每个数字 x x x的范围,是 x ∈ ( x − 1 , x , x + 1 ) x\in(x-1,x,x+1) x∈(x−1,x,x+1),那么影响 x x x变化的就是, x + 1 x+1 x+1和 x − 1 x-1 x−1,如果 x + 1 x+1 x+1和 x − 1 x-1 x−1不存在x则可变成其中的一个,增加位置。
- 不妨先将序列排序(Line 11)
- 遍历序列(Line 13)
- 如果
a[i-1]<a[i]-1
,a[i]=a[i]-1
, 因为a[i]-1
不存在,不会影响前缀(前面的序列)(Line 14) - 否则,如果
a[i-1]==a[i]-1
,a[i]
不变,a[i]
不能减1,因为a[i]-1
已经在前缀中出现(Line 16) - 否则,如果
a[i-1]==a[i]
,a[i]=a[i]+1
(Line 18) - 否则,
a[i]=a[i-1]
(即a[i-1]==a[i]+1
)(Line 20)
- 如果
- 统计
a[1:n]
中有多少个不同的数字
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int a[maxn];
int main(int argc, char const *argv[])
{
int n;cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1, a+n+1);
set<int> st;
for(int i=1;i<=n;i++){
if(a[i]-1>a[i-1]){
a[i]-=1;
} else if(a[i]-1==a[i-1]){
;
} else if(a[i]==a[i-1]){
a[i]+=1;
} else {
a[i]=a[i-1];
}
st.insert(a[i]);
}
cout<<st.size()<<endl;
return 0;
}
5.abs(a+b+c)
题面
一种方法
因为是绝对值相加,所以无法确定每一项
<
a
,
b
,
c
>
<a,b,c>
<a,b,c>的贡献;
无法确定贡献是因为不知道最后的
s
u
m
a
,
s
u
m
b
,
s
u
m
c
suma,sumb,sumc
suma,sumb,sumc的正负,
很明显了,假设我们已经知道最后的
s
u
m
a
,
s
u
m
b
,
s
u
m
c
suma,sumb,sumc
suma,sumb,sumc正负情况,那么就能确定每一项的贡献值,取前m项贡献最大的即可
枚举最后可能的结果,要么正,要么负,总共只有8种可能
算法:
- 枚举 s u m a , s u m b , s u m c suma,sumb,sumc suma,sumb,sumc正负情况,Line 34
- 计算每一项贡献,Line 14
- 计算前m项最大的贡献,Line17、18
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+7;
ll a[maxn],b[maxn],c[maxn];
int n,m;
vector<ll> temp;
bool cmp(ll a, ll b){
return a>b;
}
ll solve(int flaga,int flagb, int flagc){
// cout<<flaga<<flagb<<flagc<<endl;
temp.clear();
for(int i=1;i<=n;i++){
temp.push_back(a[i]*flaga+b[i]*flagb+c[i]*flagc);
}
sort(temp.begin(),temp.end(),cmp);
return accumulate(temp.begin(), temp.begin()+m, 0);
}
int check(int x){
if(x==0) return -1;
return 1;
}
int main(int argc, char const *argv[])
{
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i]>>b[i]>>c[i];
}
ll ans=0;
for(int i=0;i<(1<<3);i++){
ans=max(ans,solve(check(i&1),check(i&2),check(i&4)));
}
cout<<ans<<endl;
return 0;
}