YbtOJ 「基础算法」 第2章 贪心算法

贪心

A. 【例题1】奶牛晒衣服

[题目背景]

熊大妈决定给每个牛宝宝都穿上可爱的婴儿装 。但是由于衣服很湿,为牛宝宝晒衣服就成了很不爽的事情。于是,熊大妈请你(奶牛)帮助她完成这个重任。

[题目描述]

一件衣服在自然条件下用一秒的时间可以晒干 \(a\) 点湿度。抠门的熊大妈只买了一台烘衣机 。使用用一秒烘衣机可以让一件衣服额外烘干 \(b\) 点湿度(一秒晒干 \(a+b\) 湿度),但在同一时间内只能烘一件衣服。现在有 \(n\) 件衣服,第 \(i\) 衣服的湿度为 \(w_i\)(保证互不相同),要你求出弄干所有衣服的最少时间(湿度为 \(0\) 为干 )。

[输入格式]

第一行三个整数,分别为 \(n,a,b\)
接下来 \(2\)\(n+1\) 行,第 \(i\) 行输入 \(w_i\)

[输出格式]

一行,弄干所有衣服的最少时间。

[算法分析]

贪心地取每一次弄干衣服时间最长的衣服来烘干

我们并没有每次都减去整体的晾干值 只是减去了每一次烘干的值 所以while中循环条件应该是\(q.top() - a * ans > 0\)

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e3 + 5;
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}

int n , a , b , ans;
priority_queue <int> q;
signed main()
{
	n = read() , a = read() , b = read();
	for ( int i = 1 ; i <= n ; i ++ ) q.push(read());
	while ( q.top() - a * ans > 0 )
	//我们并没有每次都减去整体的晾干值 只是减去了每一次烘干的值
	{
		ans ++;
		int tmp = q.top(); q.pop();
		q.push(tmp-b);
	}
	printf ( "%d" , ans );
	return 0;
} 

B. 【例题2】雷达装置

[题目描述]

假设海岸线是一条无限延伸的直线。它的一侧是陆地,另一侧是海洋。每一座小岛是在海面上的一个点。雷达必须安装在陆地上(包括海岸线),并且每个雷达都有相同的扫描范围 \(d\)。你的任务是建立尽量少的雷达站,使所有小岛都在扫描范围之内。

数据使用笛卡尔坐标系,定义海岸线为 \(x\) 轴。在 \(x\) 轴上方为海洋,下方为陆地。

[输入格式]

第一行包括 \(2\) 个整数 \(n\)\(d\)\(n\) 是岛屿数目,\(d\) 是雷达扫描范围。

接下来 \(n\) 行,每行两个整数,为岛屿坐标。

[输出格式]

一个整数表示最少需要的雷达数目,若不可能覆盖所有岛屿,输出 -1

[算法分析]

对于每一个雷达 处理出最左和最右面能控制到它的雷达端点

之后 和线段覆盖思路相似 将雷达按照右端点排序

如果节点的左端点能被前一个放置的雷达覆盖 那么不需要再放置

反之 如果节点的左端点不能被覆盖 那么需要在这个不能被覆盖的节点的右端点放置一个雷达 这样保证了最优

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e3 + 5;
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}

int n , ans;
double d , x , y , last;

struct seg { double l , r; }a[N];//最左面可以放雷达的点 最右面可以放雷达的点

signed main()
{
	n = read() , scanf ( "%lf" , &d );
	for ( int i = 1 ; i <= n ; i ++ )
	{
		x = read() , y = read();
		if ( d < y ) { printf ( "-1" ); return 0; }
		a[i].l = x - sqrt ( d * d - y * y ) , a[i].r = x + sqrt ( d * d - y * y );
	}
	sort ( a + 1 , a + n + 1 , [](const seg a , const seg b) { return a.r < b.r; } );
	last = a[1].r , ans = 1;
	for ( int i = 2 ; i <= n ; i ++ )
		if ( a[i].l > last )
			ans ++ , last = a[i].r;
	printf ( "%d" , ans );
	return 0;
} 

C. 【例题3】畜栏预定

[题目描述]

\(N\)头牛在畜栏中吃草。每个畜栏在同一时间段只能提供给一头牛吃草,所以可能会需要多个畜栏,给出第\(i\)头牛开始吃草的时间区间\([a_i,b_i]\),求需要的最少畜栏数和每头牛对应的畜栏方案。

[输入格式]

第一行一个正整数\(N\)

接下来\(N\)行,第\(i\)行两个正整数\(a_i,b_i\)

[输出格式]

第一行一个整数,表示需要的最少畜栏数。

接下来\(N\)行,第\(i\)行一个整数表示第\(i\)头牛的对应畜栏,编号是从\(1\)开始的连续整数,方案合法即可。

[算法分析]

先将所有区间按照左端点(开始时间)排序

每一次贪心地将这一段区间接到畜栏中结束时间快的畜栏后面 如果接不上就新开一个组

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 5e4 + 5;
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}
int n , cnt , pos[N];
struct cow { int l , r , id; } a[N];
struct node { int ed , id; const bool operator < ( const node a ) const { return ed > a.ed; }  };//保证弹出的是结束时间最小的畜栏
priority_queue <node> q;


signed main ()
{
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i].l = read() , a[i].r = read() , a[i].id = i;
	sort ( a + 1 , a + n + 1 , [](const cow a , const cow b) { return a.l < b.l; } );
	for ( int i = 1 ; i <= n ; i ++ ) 
	{
		if ( q.empty() || q.top().ed >= a[i].l ) q.push ( (node) { a[i].r , ++cnt } ) , pos[a[i].id] = cnt;
		else 
		{
			pos[a[i].id] = q.top().id;
			q.pop();
			q.push ( (node) { a[i].r , pos[a[i].id] } );
		}
	}
	printf ( "%d\n" , cnt );
	for ( int i = 1 ; i <= n ; i ++ ) printf ( "%d\n" , pos[i] );
	return 0;
}

D. 【例题4】荷马史诗

追逐影子的人,自己就是影子 —— 荷马

[题目描述]

Allison 最近迷上了文学。她喜欢在一个慵懒的午后,细细地品上一杯卡布奇诺,静静地阅读她爱不释手的《荷马史诗》。但是由《奥德赛》和《伊利亚特》 组成的鸿篇巨制《荷马史诗》实在是太长了,Allison 想通过一种编码方式使得它变得短一些。

一部《荷马史诗》中有 \(n\) 种不同的单词,从 \(1\)\(n\) 进行编号。其中第 \(i\) 种单词出现的总次数为 \(w_i\)。Allison 想要用 \(k\) 进制串 \(s_i\) 来替换第 \(i\) 种单词,使得其满足如下要求:

对于任意的 \(1\leq i, j\leq n\)\(i\ne j\) ,都有:\(s_i\) 不是 \(s_j\) 的前缀。

现在 Allison 想要知道,如何选择 \(s_i\),才能使替换以后得到的新的《荷马史诗》长度最小。在确保总长度最小的情况下,Allison 还想知道最长的 \(s_i\) 的最短长度是多少?

一个字符串被称为 \(k\) 进制字符串,当且仅当它的每个字符是 \(0\)\(k-1\) 之间(包括 \(0\)\(k-1\) )的整数。

字符串 \(str1\) 被称为字符串 \(str2\) 的前缀,当且仅当:存在 \(1 \leq t\leq m\) ,使得 \(str1 = str2[1..t]\)。其中,\(m\) 是字符串 \(str2\) 的长度,\(str2[1..t]\) 表示 \(str2\) 的前 \(t\) 个字符组成的字符串。

[输入格式]

输入的第 \(1\) 行包含 \(2\) 个正整数 \(n, k\) ,中间用单个空格隔开,表示共有 \(n\) 种单词,需要使用 \(k\) 进制字符串进行替换。

接下来 \(n\) 行,第 \(i + 1\) 行包含 \(1\) 个非负整数 \(w_i\),表示第 \(i\) 种单词的出现次数。

[输出格式]

输出包括 \(2\) 行。

\(1\) 行输出 \(1\) 个整数,为《荷马史诗》经过重新编码以后的最短长度。

\(2\) 行输出 \(1\) 个整数,为保证最短总长度的情况下,最长字符串 \(s_i\) 的最短长度。

[算法分析]

哈夫曼树板题 自下而上构造 用树形结构为每一个节点赋编码

如果你是\(k\)进制的串 那么每一个节点一共有\(k\)个儿子 编码分别为\(0\)\(k-1\)

基本概念:

  • 树的路径长度PL:从树根到树的每个节点的路径长度(每条边长度为1)之和(完全二叉树为这种路径长度最短的二叉树)。

  • 树的带权路径长度WPL:树的所有叶子节点的带权路径长度(该节点到根节点路径长度与节点上权的乘积)之和。

image

这是一棵典型的二叉哈夫曼树 多叉同理

那么具体代码实现就是:每一次拿出来\(k\)个权值最小的节点并向上合并

可以证明 这样可以保证树上路径权值和(也就是类比石子合并 树根到叶子的路径乘以深度)最小

因为你\(n\)个叶子节点才是需要的 这样可以保证任何一个串不是另一个串的前缀 大的边要尽量向上放 这样才能减少整体文章长度

需要将整棵树补满成完全\(k\)叉树

那么对于循环条件 我们在合并到根之后break即可

#include <bits/stdc++.h>
using namespace std;
#define inl inline

#define int long long

const int N = 1e6 + 5; 
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

struct node
{
	int w , dep;
	friend bool operator < ( const node &a , const node &b ) { return a.w == b.w ? a.dep > b.dep : a.w > b.w; }
};

priority_queue <node> q;

int n , k , ans;

signed main ()
{
	n = read() , k = read();
	for ( int i = 1 ; i <= n ; i ++ ) q.push ( (node) { read() , 0 } );
	while ( ( q.size() - 1 ) % ( k - 1 ) != 0 ) q.push ( (node) { 0 , 0 } );
	while ( q.size() > 1 )//如果小于k了 直接赋值即可 
	{
		int val = 0 , maxdep = 0;
		for ( int i = 1 ; i <= k ; i ++ )
		{
			val += q.top().w;
			maxdep = max ( maxdep , q.top().dep );
			q.pop();
		}
		ans += val;
		q.push ( (node) { val , maxdep + 1 } );//新建一个父亲节点		
	}
	printf ( "%lld\n%lld" , ans , q.top().dep );
	return 0;
}

E. 1.排队接水

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define int long long
const int mod = 2147483647;
const int N = 2e6 + 5;
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}

int n , ans;
double ave;

struct node { int wait , id , tim; } a[N];

signed main ()
{
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = { 0 , i , read() };
	sort ( a + 1 , a + n + 1 , [](const node &a , const node &b) { return a.tim < b.tim; } );
	for ( int i = 1 ; i <= n ; i ++ )
	{
		printf ( "%d " , a[i].id );
		a[i].wait = ans;
		ans += a[i].tim;
	}
	for ( int i = 1 ; i <= n ; i ++ )
		ave += a[i].wait;
	ave /= (double) n;
	printf ( "\n%.2lf" , ave );
	return 0;
}

F. 2.砍树问题

[算法分析]

先将树按横坐标排序,则由题意得 \(ai≤min(|pi−pi−1|,|pi+1−pi|)\)

\(1\)\(n\)要特判

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 2e6 + 5;
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}

int ans , n;

struct node { int pos , hei; } a[N];

signed main ()
{
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i].pos = read() , a[i].hei = read();
	sort ( a + 1 , a + n + 1 , [](const node &a , const node &b) { return a.pos < b.pos; } );
	for ( int i = 1 ; i <= n ; i ++ )
	{
		if ( i == 1 ) ans += max ( 0 , a[i].hei - ( a[i+1].pos - a[i].pos ) );
		else if ( i == n ) ans += max ( 0 , a[i].hei - ( a[i].pos - a[i-1].pos ) );
		else ans += max ( 0 , max ( a[i].hei - ( a[i].pos - a[i-1].pos ) , a[i].hei - ( a[i+1].pos - a[i].pos ) ) );
	}
	printf ( "%d" , ans );
	return 0;
}

G. 3.出栈序列

[算法分析]

我们对于入栈序列维护一个后缀max

每一次入栈 如果这个值不小于后缀最大值 那么是最大值 pop出来 再尝试pop前一个 以此类推

最后可能没有都pop出来 那么清空栈即可

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e6 + 5;
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();如果这个值已经是最大值了 那么pop出来 再尝试pop前一个
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}

int a[N] , s[N] , n;
stack<int> sta;

int main()
{
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
    for ( int i = n ; i >= 1 ; i -- ) s[i] = max ( s[i+1] , a[i] );//后缀最大值 从后往前,a[i]-a[n]间最大值就是s[i] 
	for ( int i = 1 ; i <= n ; i ++ )
	{
		sta.push ( a[i] );
		while ( !sta.empty() && sta.top() >= s[i+1] ) printf ( "%d " , sta.top() ) , sta.pop();
		//如果这个值已经是最大值了 那么pop出来 再尝试pop前一个
	}
	//都入栈了 直接输出即可
	while ( !sta.empty() ) printf ( "%d " , sta.top() ) , sta.pop();
	return 0;
} 

H. 4.序列问题

[题目描述]

给定一个长度为\(n\)的数列和一个整数\(k\),求两个长度不小于\(k\)的连续子序列,分别使他们的二进制或运算值和二进制与运算值最大。

[输入格式]

第一行两个正整数 。

第二行\(n\)个整数,表示给定的数列。

[输出格式]

输出\(1\)个整数,分别表示最大的或运算值和最大的与运算值。

[算法分析]

对于或运算 只需要所有点或起来即可

对于与运算 我们必须强制固定选\(k\)长度的子序列 因为这样一定不劣

我们设置\(f[i][j]\)表示第\(i\)个数的第\(j\)位的二进制值

\(sum[i][j]\)表示\(1-i\)这些数的\(j\)位的和

前缀和处理每一个区间内的1 如果1=k 那么合法

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e6 + 5; 
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}

int n , k , anshuo , ansyu , a[N] , b[N][31] , sum[N][31];

signed main ()
{
	n = read() , k = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , anshuo |= a[i];
	printf ( "%d " , anshuo );
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int j = 0 ; j <= 30 ; j ++ )
		{
			b[i][j] = ( a[i] >> j ) & 1;
			sum[i][j] = sum[i-1][j] + b[i][j];
		}
	for ( int l = 1 ; l <= n - k + 1 ; l ++ )
	{
		int r = l + k - 1;//越多个数与起来显然要更小 所以严格控制在k个显然不劣
		int ans = 0;
		for ( int i = 0 ; i <= 30 ; i ++ ) ans += ( 1 << i ) * ( sum[r][i] - sum[l-1][i] == k );
		ansyu = max ( ansyu , ans );
	}
	printf ( "%d" , ansyu );
	return 0;
}
posted @ 2023-03-17 23:57  Echo_Long  阅读(219)  评论(0编辑  收藏  举报