題目
有一個由*
和+
組成的字符串,*
表示乘,表示加。
選出一個子序列,使得子序列形成的數字模最大
思考歷程
我又把“子序列”看成了“子串”……
到最後幾十分鐘寫暴力的時候,我才發現這一點……
於是最終不加思考地寫了個狀壓DP上去。
沒來得及改回來,開了的數組,編譯竟然過了???
於是就爆了。
正解
如果子序列中選擇有*++
,其實它等價於+*
於是可以做如下轉化:在原字符串中,如果遇到長度大於的+
段,就將它兩個兩個地丟到上一個*
前,直到長度爲或(保留個是爲了保留它只選擇一個的權利)。
注意一開始在序列前面加上無限個*
,顯然這對答案沒有影響。
於是字符串中的+
段就只剩下長度爲的和長度爲的。
貪心地從前往後欽定,*
用來保證位數,+
用來填。
在*
足夠的時候,肯定是儘量地在高位處填。並且這個時候不要進位,因爲保證了高位儘量填,進位會導致前功盡棄。
這樣一直填到不夠的時候,就將剩下的整個字符串都加進來。這時候可以有進位,但每一位最多隻可能進一位,並且不可能進到之前儘量填的部分。
於是這題就做完了。
代碼
有一說一這題用指針打特別爽。
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;
}
總結
不要總是犯把“子序列”看成“子串”這樣幼稚的錯誤啊……