题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3183

题解:

方法一:贪心。

在草稿纸上试多几次可以知道,删除数字中从左到右最后一位递增(可以等于)的数字,可以得到最小值,在这个基础下,又继续删除最后一位递增的数字,得到的依然是最小值。这就表明当前这步的贪心不仅是当前最优,而且对于下一步贪心来说也是最优的。所以每次删除最后递增项就可以了。

初期代码(每次循环找最后递增项):

Accepted 3183 46MS 1408K 1259 B G++
#include<cstdio>//hdu3183 贪心,删除不严格递增序列的最后一个元素
#include<cstring>
#include<algorithm>
#define MAX(a,b) (a>b?a:b)
#define LL long long
#define mod 1000000007 using namespace std; int main()
{
int n,m;
char dig[1005],ans[1005];
while(scanf("%s%d",dig,&m)!=EOF)
{
n = strlen(dig);
if(n<=m)
{
puts("0");
continue;
} for(int i = 0; i<m; i++)
{
//每次从头开始找递增序列的最后一个元素
int j = 0,last = 0,de = 0;
for(j = 1;j<=n-1; j++)
{
if(dig[j]==0) continue;
if(dig[last]<=dig[j])//用last记录上次的最后一个递增元素,以便跳过已经被删除的元素
last = j;
else break;
}
dig[last] = 0;//将递增序列的最后一个元素标记,删除
}
int cnt = 0;
for(int i = 0; i<n; i++)//将未被删除的导入数组中,
if(dig[i]) ans[cnt++] = dig[i]; int j = 0;
while(j<cnt-1 && ans[j]=='0')//跳过前导0,但要留最后一位,因为答案可能就为0
j++;
while(j<cnt)
putchar(ans[j++]);
putchar('\n');
}
return 0;
}

后来发现:每一次都循环找出递增项,其实已经重复操作了。因为在上一次删除中,前面的数字肯定是递增的,这就不用再重新扫一次了,只需要判断当前数字是否也递增,如果递增,则继续下一个数字,如果不是,则将前面的数字删除,直到前面的数字<=当前数字或者删除完毕。这样单调队列就派上用场了。

Accepted 3183 15MS 1404K 1003 B G++

代码如下:

#include<cstdio>//hdu3183 单调队列
#include<cstring>
#include<algorithm>
#define MAX(a,b) (a>b?a:b)
#define LL long long
#define mod 1000000007 using namespace std; char q[1005]; int main()
{
int n,m;
char a[1005];
while(~scanf("%s%d",a,&m))
{
n = strlen(a);
if(n<=m)
{
puts("0");
continue;
} int rear = 0, cnt = 0;
int i;
for(i = 0; i<n; i++)
{
while(rear>0 && cnt<m && a[i]<q[rear])
rear--, cnt++;
if(cnt==m) break; q[++rear] = a[i];
}
while(rear>0 && cnt<m)//没有删除够,继续删
rear--, cnt++; while(rear>0)//将队列里的元素倒入数组中,准备输出
a[--i] = q[rear--]; while(i<=n-2 && a[i]=='0') i++;//跳过前导0;但要留最后一位,因为答案可能就为0
for(;i<n; i++)
putchar(a[i]);
putchar('\n');
}
return 0;
}

方法二:RMQ or 线段树

问题可以转化为:在这n个数字中选n-m个数(只能从左往右一次选),使得组成的数最小。

可知第一个数字必定在0~n-1-(m-1),即0~n-m之内取得,且取最小的数字。设第一个数取得的位置为pos,则取得第二个数的范围为:pos+1~n-m+1, 然后又将pos设为取得第二个数的位置,则取得第三个数的范围为:pos+1~n-m+2 …………

查询区间最小值可以用RMQ或者线段树实现。

RMQ:

#include<cstdio>//hdu3183 RMQ
#include<cstring>
#include<algorithm>
#include<cmath>
#define MIN(a,b) (a<b?a:b)
#define LL long long
#define mod 1000000007 using namespace std; char s[1005], ans[1005];
int n,m,st[1005][20];//st存最值得下标 int Get_min(int x, int y)
{
return (s[x]<=s[y]?x:y);
} int init_RMQ()
{
for(int i = 0; i<n; i++)
st[i][0] = i; for(int j = 1; (1<<j)<n; j++)
for(int i = 0; i+(1<<j)-1<n; i++)
st[i][j] = Get_min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
} int find_k(int le, int ri)
{
int k = log(ri-le+1)/log(2);
return Get_min(st[le][k],st[ri-(1<<k)+1][k]);
} int main()
{
while(~scanf("%s%d",s,&m))
{
n = strlen(s);
m = n-m;
init_RMQ(); int pos = 0,cnt = 0;
while(m)
{
pos = find_k(pos,n-m);
ans[cnt++] = s[pos++];
m--;
} int i = 0;
for(; i<cnt-1; i++)
if(ans[i]!='0') break;
if(cnt==0)
putchar('0');
else for(; i<cnt; i++)
putchar(ans[i]);
putchar('\n');
}
return 0;
}

线段树:

注意:在建树时,下标为mid的元素要归到左边去。

如果归到右边:

设le=3,ri=4;

mid = (le+ri)/2 = 3;

build(le,mid-1); //实际为: build(3,2) 出错

build(mid,ri);//实际为:build(3,4),即又为原始的le和ri, 永久执行下去……

代码如下:

#include<cstdio>//hdu3183 线段树
#include<cstring>
#include<cmath>
#include<algorithm>
#define LL long long using namespace std; int n,m;
char s[1005], ans[1005]; struct node
{
int pos,le,ri;
}tree[4005]; void build(int u, int le ,int ri)
{
tree[u].le = le;//将结点所指向的范围保存到结点中
tree[u].ri = ri; if(le==ri)
{
tree[u].pos = le;
return;
} int mid = (le+ri)/2;
build(u*2,le,mid);//左右建树
build(u*2+1,mid+1,ri); if(s[tree[u*2].pos]<=s[tree[u*2+1].pos])
tree[u].pos = tree[u*2].pos;
else
tree[u].pos = tree[u*2+1].pos;
} int query(int u,int x, int y)
{
int le = tree[u].le, ri = tree[u].ri;
if(le==x && ri==y)
return tree[u].pos; int mid = (le+ri)/2;
if(y<=mid) return query(u*2,x,y);//查询范围在左边
else if(x>mid) return query(u*2+1,x,y);//查询范围在右边
//else return (s[query(u*2,x,mid)]<=s[query(u*2+1,mid+1,y)]?tree[u*2].pos:tree[u*2+1].pos); //有误
else//查询范围被分成两段
{
int xx = query(u*2,x,mid);
int yy = query(u*2+1,mid+1,y);
if(s[xx]<=s[yy]) return xx;
return yy;
}
} int main()
{
while(~scanf("%s%d",s,&m))
{
n = strlen(s);
m = n-m;
build(1,0,n-1); int pos = 0,cnt = 0;
while(m)
{
pos = query(1,pos,n-m);
ans[cnt++] = s[pos++];
m--;
} int i = 0;
for(; i<cnt-1; i++)
if(ans[i]!='0') break;
if(cnt==0)
putchar('0');
else for(; i<cnt; i++)
putchar(ans[i]);
putchar('\n');
}
return 0;
}

HDU3183 A Magic Lamp —— 贪心(单调队列优化)/ RMQ / 线段树的更多相关文章

  1. USACO 2009 Open 干草塔 Tower of Hay(贪心+单调队列优化DP)

    https://ac.nowcoder.com/acm/contest/1072/B Description 为了调整电灯亮度,贝西要用干草包堆出一座塔,然后爬到牛棚顶去把灯泡换掉.干草包会从传送带上 ...

  2. BZOJ1233 [Usaco2009Open]干草堆tower[贪心+单调队列优化]

    地址 注意思路!多看几遍! 很巧妙的一道题.不再是决策点以dp值中一部分含j项为维护对象,而是通过维护条件来获取决策. 首先有个贪心策略,让底层的宽度尽可能小,才能让高度尽可能高.所以应该倒着dp,表 ...

  3. 完美字符子串 单调队列预处理+DP线段树优化

    题意:有一个长度为n的字符串,每一位只会是p或j.你需要取出一个子串S(注意不是子序列),使得该子串不管是从左往右还是从右往左取,都保证每时每刻已取出的p的个数不小于j的个数.如果你的子串是最长的,那 ...

  4. 【单调队列优化dp】 分组

    [单调队列优化dp] 分组 >>>>题目 [题目] 给定一行n个非负整数,现在你可以选择其中若干个数,但不能有连续k个数被选择.你的任务是使得选出的数字的和最大 [输入格式] ...

  5. BZOJ1233 [Usaco2009Open]干草堆tower 【单调队列优化dp】

    题目链接 BZOJ1233 题解 有一个贪心策略:同样的干草集合,底长小的一定不比底长大的矮 设\(f[i]\)表示\(i...N\)形成的干草堆的最小底长,同时用\(g[i]\)记录此时的高度 那么 ...

  6. LUOGU P2569 [SCOI2010]股票交易(单调队列优化dp)

    传送门 解题思路 不难想一个\(O(n^3)\)的\(dp\),设\(f_{i,j}\)表示第\(i\)天,手上有\(j\)股的最大收益,因为这个\(dp\)具有单调性,所以\(f_i\)可以贪心的直 ...

  7. 【笔记篇】单调队列优化dp学习笔记&&luogu2569_bzoj1855股票交♂易

    DP颂 DP之神 圣洁美丽 算法光芒照大地 我们怀着 崇高敬意 跪倒在DP神殿里 你的复杂 能让蒟蒻 试图入门却放弃 在你光辉 照耀下面 AC真心不容易 dp大概是最经久不衰 亘古不化的算法了吧. 而 ...

  8. POj3017 dp+单调队列优化

    传送门 解题思路: 大力推公式:dp[i]=min(dp[k]+max(k+1,i)){k>=0&&k<i},max(j,i)记为max(a[h]){h>k& ...

  9. Codeforces 1304F1/F2 Animal Observation(单调队列优化 dp)

    easy 题目链接 & hard 题目链接 给出一张 \(n \times m\) 的矩阵,每个格子上面有一个数,你要在每行选出一个点 \((i,t)\),并覆盖左上角为 \((i,t)\), ...

随机推荐

  1. Unity3D Shader 入门之简单案例的实现(通过法线实现颜色变化)

    在没有接触Unity3D  Shader 之前,总感觉shader特别神奇,因为听说是对渲染流水线进行编程,就是对GPU进行编程.听着特别高大上.这不,最近刚刚接触Shader,学了几个小案例,然后本 ...

  2. man

    Description n间房子高度不同,Man 要从最矮的房子按照高度顺序跳到最高的房子,你知道房子的顺序,以及Man一次最远可以跳多远,相邻的房子至少有1的距离,房子的宽不计,现在由你安排相邻房子 ...

  3. js中多行字符串拼接

    前言 我们会经常遇到这样的场景,需要拼接多行字符串,在字符串中动态插入一些数据以达到业务的需求.但是js中并没有标准的多行编辑的函数,于是聪明的程序员们便脑洞大开,书写出许多有趣的方法. 1 2 3 ...

  4. fastdfs-zyc监控系统的使用

    原文:http://blog.csdn.net/foreversunshine/article/details/51907659 写在前面 前面有介绍过怎么安装与使用FastDFS来进行分布式的文件存 ...

  5. CodeSign error: code signing is required for product type 'Application' in SDK 'iOS 7.0'

    这个一般是证书设置的问题, 在build settings中找到 Code Signing->Code Signing Identity修改成有效的证书即可

  6. 邁向IT專家成功之路的三十則鐵律 鐵律十二:IT人養生之道-德行

    所謂的「養生」在中國古代裡所指的是針對內在精神層面修為的提升,到了近代中醫所謂的養生,則除了包含最根本的內在精神層面之外,還涵蓋了外在身體的養護.在現今各行各業的人士當中,嚴格來說都應該要有一套專屬的 ...

  7. 转:Android IOS WebRTC 音视频开发总结 (系列文章集合)

    随笔分类 - webrtc   Android IOS WebRTC 音视频开发总结(七八)-- 为什么WebRTC端到端监控很关键? 摘要: 本文主要介绍WebRTC端到端监控(我们翻译和整理的,译 ...

  8. codefoeces B. Friends and Presents

    B. Friends and Presents time limit per test 1 second memory limit per test 256 megabytes input stand ...

  9. 每天进步一点点——Linux中的线程局部存储(二)

    转载请说明出处:http://blog.csdn.net/cywosp/article/details/26876231     在Linux中另一种更为高效的线程局部存储方法,就是使用keyword ...

  10. Node.js知识点学习

    Node.js知识点学习 一.基本概念 Node.js,或者 Node,是一个可以让 JavaScript 运行在服务器端的平台.可以说,Node.js开创了javascript模块化开发的先河,早期 ...