HDU3183 A Magic Lamp —— 贪心(单调队列优化)/ RMQ / 线段树
題目鏈接: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 1000000007using 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,但要留最后一位,因為答案可能就為0j++;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 1000000007using 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;但要留最后一位,因為答案可能就為0for(;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 1000000007using 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 longusing 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;elsetree[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; }?
轉載于:https://www.cnblogs.com/DOLFAMINGO/p/7538744.html
總結
以上是生活随笔為你收集整理的HDU3183 A Magic Lamp —— 贪心(单调队列优化)/ RMQ / 线段树的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mybatis xml注释sql 的注意
- 下一篇: Android添加Header请求参数实