第七章 贪心法课后作业

第七章 贪心法课后作业

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的临点vv到起点的距离为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(x1,x,x+1),那么影响 x x x变化的就是, x + 1 x+1 x+1 x − 1 x-1 x1,如果 x + 1 x+1 x+1 x − 1 x-1 x1不存在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;
}
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值