题目
有一个由*
和+
组成的字符串,*
表示乘
2
2
2,
+
+
+表示加
1
1
1。
选出一个子序列,使得子序列形成的数字模
2
k
2^k
2k最大
n
,
k
≤
1
e
6
n,k\leq 1e6
n,k≤1e6
思考历程
我又把“子序列”看成了“子串”……
到最后几十分钟写暴力的时候,我才发现这一点……
于是最终不加思考地写了个状压DP上去。
没来得及改回来,开了
1
e
6
∗
1024
1e6*1024
1e6∗1024的数组,编译竟然过了???
于是就爆
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;
}
总结
不要总是犯把“子序列”看成“子串”这样幼稚的错误啊……