6692. 【2020.06.05省选模拟】灵符「无寿之梦」

题目

有一个由*+组成的字符串,*表示乘 2 2 2 + + +表示加 1 1 1
选出一个子序列,使得子序列形成的数字模 2 k 2^k 2k最大
n , k ≤ 1 e 6 n,k\leq 1e6 n,k1e6


思考历程

我又把“子序列”看成了“子串”……
到最后几十分钟写暴力的时候,我才发现这一点……
于是最终不加思考地写了个状压DP上去。
没来得及改回来,开了 1 e 6 ∗ 1024 1e6*1024 1e61024的数组,编译竟然过了???
于是就爆 0 0 0了。


正解

如果子序列中选择有*++,其实它等价于+*
于是可以做如下转化:在原字符串中,如果遇到长度大于 2 2 2+段,就将它两个两个地丢到上一个*前,直到长度为 1 1 1 2 2 2(保留 2 2 2个是为了保留它只选择一个的权利)。
注意一开始在序列前面加上无限个*,显然这对答案没有影响。

于是字符串中的+段就只剩下长度为 1 1 1的和长度为 2 2 2的。
贪心地从前往后钦定,*用来保证位数,+用来填 1 1 1
*足够的时候,肯定是尽量地在高位处填 1 1 1。并且这个时候不要进位,因为保证了高位尽量填,进位会导致前功尽弃。
这样一直填到 ∗ * 不够的时候,就将剩下的整个字符串都加进来。这时候可以有进位,但每一位最多只可能进一位,并且不可能进到之前尽量填 1 1 1的部分。

于是这题就做完了。


代码

有一说一这题用指针打特别爽。

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 1000010
int n,K;
char str[N];
struct Node{
	Node *pre,*suc;
	bool op;
	int w;
	int rem;
};
Node *fir,*lst;
int ans[N];
int main(){
	freopen("dream.in","r",stdin);
	freopen("dream.out","w",stdout);
	scanf("%d%d%s",&n,&K,str+1);
	for (int i=1;i<=n;++i)
		str[i]=(str[i]=='+');
	lst=fir=new Node;
	*fir={NULL,NULL,0,1000000000};
	for (int i=1,cnt=0;i<=n;++i)
		if (lst->op==str[i])
			lst->w++;
		else{
			Node *nw=new Node;
			*nw={lst,NULL,(bool)str[i],1};	
			lst->suc=nw;
			lst=nw;
		}
	for (Node *p=lst;p;p=p->pre)
		if (p->op==1 && p->w>2){
			int d=(p->w&1?p->w-1>>1:p->w-2>>1);
			p->w-=d*2;
			if (p->pre->w==1)
				p->pre->pre->w+=d;
			else{
				Node *nw1=new Node,*nw2=new Node;
				*nw1={p->pre,nw2,1,d};
				*nw2={nw1,p,0,1};
				p->pre->w--;
				p->pre->suc=nw1;
				p->pre=nw2;
			}
		}
	int cntplus=lst->op;
	lst->rem=(lst->op==0?lst->w:0);
	for (Node *p=lst->pre;p;p=p->pre){
		cntplus+=p->op;
		p->rem=p->suc->rem+(p->op==0?p->w:0);
	}
	if (cntplus>=K){
		for (int i=0;i<K;++i)
			putchar('1');
		return 0;
	}
	int i=K;
	for (Node *p=fir;p;p=p->suc)
		if (p->op){
			if (p->rem>=i-1)
				ans[--i]=1;	
			else
				ans[p->rem]+=p->w;
		}
	for (int i=0;i<K;++i){
		ans[i+1]+=ans[i]>>1;
		ans[i]&=1;
	}
	i=K-1;
	for (;i>=0;--i)
		if (ans[i])
			break;
	if (i<0)
		putchar('0');
	else
		for (;i>=0;--i)
			putchar(ans[i]+'0');
	return 0;
}

总结

不要总是犯把“子序列”看成“子串”这样幼稚的错误啊……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值