20201019 day39 模拟(十二)&&复习9:贪心综合练习(一)
贴上一个艰苦卓绝的做题过程
- P1080 国王游戏
- P1376 [USACO05MAR]Yogurt factory 机器工厂
- P1106 删数问题
- P1209 [USACO1.3]修理牛棚 Barn Repair
- P1842 [USACO05NOV]奶牛玩杂技
1 修理牛棚
problem
长\(s\)的01序列,共\(c\)个1,给定1的位置。用\(m\)条线段遮住所有的1,求最短线段长度和。
solution
找出最远长度,删除其中最长的\(m-1\)个空隙。贪心想法。
thoughts
70pts:长度处理错误。3和43之间其实是41个空隙。
70pts:一开始给定的1的位置并不是递增的。没排序,导致负数。
100pts:正解。优先队列可做。
code
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
int read(){
int a=0,op=1;
char c;c=getchar();
while(c<'0'||c>'9'){
if(c=='-') op=-1;c=getchar();
}
while(c>='0'&&c<='9') a*=10,a+=c^48,c=getchar();
return a*op;
}
int m,s,c,ans,kmin,kmax;
int bor[405],cha[405];
bool cmp(int x,int y){return x>y;}
bool cmp2(int x,int y){return x<y;}
int main(){
m=read(),s=read(),c=read();
for(int i=1;i<=c;i++){
int k=read();
bor[i]=k;
}
sort(bor+1,bor+c+1,cmp2);
ans=bor[c]-bor[1]+1;
//printf("%d\n",ans);
for(int i=2;i<=c;i++){
cha[i-1]=bor[i]-bor[i-1]-1;
}
//for(int i=1;i<=c-1;i++) printf("%d ",cha[i]);
//printf("\n");
sort(cha+1,cha+c,cmp);
for(int i=1;i<=m-1;i++) ans-=cha[i];
printf("%d",ans);
return 0;
}
//00110 10100 00011 11000 10001 11001 10000 00001 111
2 删数问题
problem
给定\(N\)位数字,删去其中\(k\)个数使得剩下的数最小。\(N\le 250\)。
thoughts
28pts:没有考虑前导0.想法是记录每个位置\(i\)前面有\(f(i)\)个0,\(i-f(i)\)表示前\(i\)位有多少不是0,若\(i-f(i)\le k\),说明可删去前面的所有数,\(k\)减去\(i-f(i)\)。实现失败,正确性待证明。
42pts:优化上述方案,特判0的个数进行分开处理。
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
int read(){
int a=0,op=1;
char c;c=getchar();
while(c<'0'||c>'9'){
if(c=='-') op=-1;c=getchar();
}
while(c>='0'&&c<='9') a*=10,a+=c^48,c=getchar();
return a*op;
}
int n,k;
char s[255];
struct node{
int val,key;
}num[255];
bool cmp(node x,node y){return x.val>y.val;}
bool cmp2(node x,node y){return x.key<y.key;}
int zero_num=0,zero_key[405];
int main(){
scanf("%s\n%d",s+1,&k);
int len=strlen(s+1);
for(int i=1;i<=len;i++){
num[i].val=s[i]-'0';
num[i].key=i;
}
for(int i=1;i<=len;i++){
if(num[i].val==0) zero_num++;
zero_key[i]=zero_num;
}
for(int i=1;i<=len;i++){
printf("%d ",zero_key[i]);
}
printf("\n%d\n",zero_num);
if(zero_num!=0){
int tims=0;
for(int i=len;i>=1;i--){
printf("%d ",i-zero_key[i]);
if(i-zero_key[i]<=k&&zero_key[i]!=0){
for(int j=i;j>=1;j--){
if(num[j].val!=0) tims++;
num[j].val=0;
printf("%d\n",tims);
}
break;
}
}
k-=tims;
}
for(int i=1;i<=len;i++) printf("%d",num[i].val);
printf("\n");
sort(num+1,num+len+1,cmp);
for(int i=1;i<=k;i++) num[i].val=-1;
sort(num+1,num+len+1,cmp2);
for(int i=1;i<=len;i++)
if(num[i].val!=-1)
printf("%d",num[i].val);
return 0;
}
solution
100pts:考虑正确的贪心算法。删去数字其实就是选择数字,枚举所有的开头,选择最小的一个,因为长度相等的时候第一位小那么整个数字都会小。以此类推向后枚举。时间复杂度\(O(N^2)\)。ACcode做法。
100pts++:题解发现的优化。\(O(n)\)算法
设\(n=\left\lceil \lg N\right\rceil\),也就是\(N\)的位数。???这是什么奇怪的知识
思想是如果一个数比后面一个数大就删掉。考虑一个数可以删掉哪些数。
如果第\(i\)个数比第\(i-1\)个数大,我们叫做第\(i\)个数可以删掉第\(i-1\)个数。这样,我们就可以采用一个数暴力往前跳然后删除的算法。
于是我们发现维护这个序列,支持快速单点删除和快速求前驱后继。显然可以使用双向链表。这样做是\(O(n)\)的,因为这样遍历一个数就会删掉它,每个数最多被删除一次,而只有\(n\)个数,删除为\(O(1)\)。
#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdio>
using namespace std;
string s;
int n, k, nxt[1000005], prv[1000005];
void Solve() {
int start = 0;
for (int i = 0;i < n - 1;i++) {
nxt[i] = i + 1;
prv[i + 1] = i;//双向链表
}
prv[0] = -1;
nxt[n - 1] = -1;
for (int i = 1;i != -1;i = nxt[i]) {
int cur = prv[i];
//往前跳并删除
while (cur >= 0 && s[i] < s[cur]) {
//printf("delete %d\n", cur);
cur = prv[cur];
k--;
if (k == 0) break;
}
if (cur >= 0) {
nxt[cur] = i;
prv[i] = cur;
} else {
start = i;
prv[i] = -1;
}
if (k == 0) break;
//printf("%d\n", i);
}
int siz = 0;
for (int i = start;i != -1;i = nxt[i]) {
siz++;
}
bool flag = 0;
siz -= k;
for (int i = start;i != -1 && siz;i = nxt[i], siz--) {
if (s[i] != '0') flag = 1;
if (flag) putchar(s[i]);
}
if (!flag) putchar('0');
}
int main() {
cin >> s >> k; n = s.length();
Solve();
return 0;
}
100pts+++:\(ST\)表可做。不会。
AC code
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
int read(){
int a=0,op=1;
char c;c=getchar();
while(c<'0'||c>'9'){
if(c=='-') op=-1;c=getchar();
}
while(c>='0'&&c<='9') a*=10,a+=c^48,c=getchar();
return a*op;
}
int n,k;
char s[505];
int num[505];
int ans[505],lst_key=-1,ed=0,lim,minn;
int main(){
scanf("%s",s);k=read();
int len=strlen(s);
int bl=len-k;//要保留的数字个数
for(int i=0;i<len;i++) num[i]=s[i]-'0';
while(bl-ed){
minn=2e9;
for(int i=lst_key+1;i<=len-bl+ed;i++)
if(num[i]<minn) minn=num[lst_key=i];
ans[ed++]=num[lst_key];
}
int p=0;
while(ans[p]==0&&bl-p-1) p++;
for(int i=p;i<bl;i++) printf("%d",ans[i]);
return 0;
}
3
problem
第\(i\)周制造一台机器花费\(C_i\),每周保管费均为\(S\),第\(i\)周需要\(Y_i\),求\(m\)周的最小花费。
solution
贪心。本周的最低价是本周的价格或者是上一周的最低价+保养费用。
thoughts
一次AC。
code
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
int read(){
int a=0,op=1;
char c;c=getchar();
while(c<'0'||c>'9'){
if(c=='-') op=-1;c=getchar();
}
while(c>='0'&&c<='9') a*=10,a+=c^48,c=getchar();
return a*op;
}
long long ans;int n,m,k,x,y;
int main()
{
n=read(),m=read();
for (int i=1;i<=n;i++){
x=read(),y=read();
if (i==1) k=x;
else k=min(k+m,x); //以k为当前最小代价
ans+=k*y;
}
printf("%lld",ans);
}
4 奶牛叠罗汉
problem
每个牛有体重\(W_i\)和力量\(S_i\),每个牛的压扁指数为它上方牛体重总和减去自身力量。一堆牛的压扁指数为所有压扁指数的最大值。求一堆牛的最小压扁指数。
solution
贪心。因为一单位的体重可以用一单位的力量来支撑,可以用\(S+W\)来排序。
证明:假设\(x,y\)为相邻的奶牛,且\(W_x+S_x<W_y+S_y\),那么\(W_x-S_y<W_y-S_x\),显然右边更优,就应该\(x\)在上,\(y\)在下。
thoughts
60pts:ans没设极小值。
70pts:ans设成\(-1\),实际上会有比这还小的数。
100pts:ans设成-inf.
code
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
#define inf 1000000000
int read(){
int a=0,op=1;
char c;c=getchar();
while(c<'0'||c>'9'){
if(c=='-') op=-1;c=getchar();
}
while(c>='0'&&c<='9') a*=10,a+=c^48,c=getchar();
return a*op;
}
const int maxn=5e4+10;
int n,ans=-inf,tot=0;
struct node{
int w,s,sum;
}cow[maxn];
bool cmp(node x,node y){
return x.sum<y.sum;
}
int main(){
n=read();
for(int i=1;i<=n;i++) cow[i].w=read(),cow[i].s=read();
for(int i=1;i<=n;i++) cow[i].sum=cow[i].s+cow[i].w;
for(int i=1;i<=n;i++) tot+=cow[i].w;
sort(cow+1,cow+1+n,cmp);
for(int i=n;i>=1;i--){
tot-=cow[i].w;
ans=max(tot-cow[i].s,ans);
}
printf("%d",ans);
return 0;
}
5 国王游戏
problem
国王排在最前面,其他大臣按一定顺序排列。每个大臣得到前面所有人左手数字乘积除以右手数字向下取整的金币。求得到最多金币大臣的金币数最少。
solution
对于国王和他后面的两个人:
第一种情况:
Left | Right | |
---|---|---|
king | \(a_0\) | \(b_0\) |
1 | \(a_1\) | \(b_1\) |
2 | \(a_2\) | \(b_2\) |
答案\(ans_1=\max\left( \dfrac{a_0}{b_1},\dfrac{a_0a_1}{b_2}\right)\) | ||
第二种情况: | ||
Left | Right | |
---- | ---- | ---- |
king | \(a_0\) | \(b_0\) |
2 | \(a_2\) | \(b_2\) |
1 | \(a_1\) | \(b_1\) |
答案\(ans_2=\max\left( \dfrac{a_0}{b_2},\dfrac{a_0a_2}{b_1}\right)\) | ||
对比答案: |
替换得
显然可以得到
即
如果\(ans_1<ans_2\),易得:
变形得
该推理过程充要,即为\(a_1b_1<a_2b_2\Leftrightarrow ans_1<ans_2\)
所以为了让\(ans\)去到最小值,我们以\(a_ib_i\)排序取最小值即可。
thoughts
50pts:没写高精度。按右手数字排序了,不正确。正确性待证明。
60pts:正确算法。没写高精度。
100pts:高精度。(结构体写法!!记得复习!!)
code
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
int read(){
long long a=0,op=1;char c=getchar();
while(c>'9'||c<'0') {if(c=='-') op=-1;c=getchar();}
while(c>='0'&&c<='9'){a*=10,a+=c^48,c=getchar();}
return a*op;
}
int now[20010],sum[20010],ans[20010],add[20010];
struct Node {
int a;
int b;
long long a_b;
}node[1010];
void times(int x) {
memset(add,0,sizeof(add));
for(int i=1;i<=ans[0];i++) {
ans[i]=ans[i]*x;
add[i+1]+=ans[i]/10;
ans[i]%=10;
}
for(int i=1;i<=ans[0]+4;i++) {
ans[i]+=add[i];
if(ans[i]>=10) {
ans[i+1]+=ans[i]/10;
ans[i]%=10;
}
if(ans[i]!=0) {
ans[0]=max(ans[0],i);
}
}
return ;
}
int divition(int x) {
memset(add,0,sizeof(add));
int q=0;
for(int i=ans[0];i>=1;i--) {
q*=10;
q+=ans[i];
add[i]=q/x;
if(add[0]==0 && add[i]!=0) {
add[0]=i;
}
q%=x;
}
return 0;
}
bool compare() {
if(sum[0]==add[0]) {
for(int i=add[0];i>=1;i--) {
if(add[i]>sum[i]) return 1;
if(add[i]<sum[i]) return 0;
}
}
if(add[0]>sum[0]) return 1;
if(add[0]<sum[0]) return 0;
}
void cp () {
memset(sum,0,sizeof(sum));
for(int i=add[0];i>=0;i--) {
sum[i]=add[i];
}
return ;
}
bool cmp(Node a,Node b) {
return a.a_b<b.a_b;
}
int main() {
int n=read();
for(int i=0;i<=n;i++) {
node[i].a=read(),node[i].b=read();
node[i].a_b=node[i].a*node[i].b;
}
sort(node+1,node+n+1,cmp);
ans[0]=1,ans[1]=1;
for(int i=1;i<=n;i++) {
times(node[i-1].a);
divition(node[i].b);
if(compare()) {
cp();
}
}
for(int i=sum[0];i>=1;i--)
printf("%d",sum[i]);
return 0;
}