P1323 删数游戏(技巧)

#技巧:给你一个数,要求你删去$m$位使得剩下的数最大

这是一个贪心问题,假设原来的数字是\(k\)位,那么相当于要保留\(k-m\)位。
有下面几种贪心策略
\(1.\)每次找最大的保留,直到\(k-m\)个,这样显然是错的,因为要求删除后顺序不能改变。
\(2.\)找到最大的且最靠前的位置,保留它,再从它后面这样操作,直到选够\(k-m\),这样显然也是错的,假如要保留\(4\)位,数据为\(123987\),直接选取\(9\)没办法凑够\(k-m\)
于是有了下面这种贪心:

\(L=1,R=m+1\)
考虑\(1-m+1\)位必须选取一个,如果不选取那么后面的\(k-m-1\)位都选也凑不够\(k-m\),所以在前\(1-m+1\)位选取一个最大的数,记他的位置为\(i\),将它加入答案,然后让\(L=i+1,R++\)
这样为什么是对的?
\(1.\)合法性,第一次要在\(1-m+1\)里选择一个数,第二次在\(i+1-m+2\)里选择一个数。每次选择的时候区间\(L-R\)至少有一个数,一定可以选择,每次选择且只选择\(1\)个。如此选了\(m-k\)次,每次右端点\(+1\),一定可以选出长度为\(m-k\)的答案\((1)\)
而且每次选完保证下一次在这个数的后面选,不会出现顺序不合法的情况\((2)\)
\(2.\)最优性,每个数都会被考虑,比如“第一次要在\(1-m+1\)里选择一个数,第二次在\(i+1-m+2\)里选择一个数”这个过程,根据答案合法性,第一个数一定出自\(1-m+1\),第二个数一定出自\(1-m+2\)所以这样控制区间没有情况会被漏下,且每次选取最大值保证了合法的基础上最优。

总复杂度最坏是\(O(n^2)\),最好是\(O(n)\)

下面是一道相关题目
题面

\(Code\)

#include<cstdio>
#include<iostream>
#include<queue>
#define maxn 30010
#define re register
using namespace std;
priority_queue<int,vector<int>,greater<int> > q;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int m,k,tmp,CNT,cnt;
int need,t[maxn],a[maxn];
char s[10001000],tcc[10001000];
void get_num(int x)
{
	CNT=0;
	while(x)
	{
	    tcc[++CNT]='0'+x%10;
		x/=10; 
	}//注意这里是倒序,别弄错了 
	for(re int i=CNT;i>=1;--i) s[++cnt]=tcc[i];
} 
int main()
{
	k=read(),m=read();
	q.push(1);
	for(re int i=1;i<=k;++i)
	{
		tmp=q.top();
		q.pop();
	    get_num(tmp);
	    a[i]=tmp;
		q.push(2*tmp+1);
		q.push(4*tmp+5);		
	}
	int L=1,R=m+1;
	for(re int i=1;i<=k;++i) printf("%d",a[i]);
	printf("\n");
    while(L<=R&&R<=cnt)
	{
		int maxx=0;
		for(re int i=L;i<=R;++i)
		{
			if((s[i]-'0')>maxx) {maxx=s[i]-'0';L=i+1;}//下次从这后面遍历就行 
		}
		printf("%d",maxx);
		R++;//下面这个区间也一定有一个解 
	} 
	return 0;
}
posted @ 2019-10-21 10:49  __Liuz  阅读(186)  评论(0编辑  收藏  举报