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("
");
    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;
}
原文地址:https://www.cnblogs.com/Liuz8848/p/11712125.html