Description
Solution
考虑两个序列(下标为111~nnn,左边为高位):
x:1100101x:1100101x:1100101
y:1001011y:1001011y:1001011
u v\ \ \ \ \ \ \ u\ \ v u v
它的距离如何计算?
先找到最小的vvv使得xv<yvx_v<y_vxv<yv,再找到比vvv小最大的uuu使得xu>yux_u>y_uxu>yu,那么最优策略是将xi=1(i>u)x_i=1(i>u)xi=1(i>u)全部抹掉,然后使x−1x-1x−1,使得∀i>uxi=1\forall_{i>u} x_i=1∀i>uxi=1,最后把yi=1(i>u)y_i=1(i>u)yi=1(i>u)的xix_ixi抹掉。
记cntxcnt_xcntx、cntycnt_ycnty分别位xxx、yyy中111的个数,距离即为cntx−cnty+n−ucnt_x-cnt_y+n-ucntx−cnty+n−u。
有了这个我们就可以dp了,设fi,j=0..1,k=0..2f_{i,j=0..1,k=0..2}fi,j=0..1,k=0..2表示从后往前做到了第iii位(低位往高位转移),jjj表示不考虑iii~nnn位时xxx是否小于yyy(j=1j=1j=1表示小于),k=2k=2k=2表示uuu,vvv都还没出现,k=1k=1k=1表示vvv已出现但uuu未出现,k=2k=2k=2表示uuu,vvv都已出现。
fff的转移直接考虑状态意义即可。
重要的是距离和的转移,重新考虑+n−u+n-u+n−u的含义,它代表在k>0k>0k>0时当前的iii都会对前面uuu有贡献,所以在这种情况下直接贡献1的距离即可。
Code
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define fo(i,j,k) for(int i=j;i<=k;++i)
#define fd(i,j,k) for(int i=j;i>=k;--i)
using namespace std;
typedef long long ll;
const int N=550,mo=1e9+7;
char s[N];
void inc(int &x,int y){
x=x+y>=mo?x+y-mo:x+y;
}
struct node{
int f,g;
node(int _f=0,int _g=0) {f=_f,g=_g;}
void up(const node x,int t){
inc(f,x.f),inc(g,x.g);
for(;t--;inc(g,x.f));
}
}f[N][2][3];
int main()
{
scanf("%s",s+1);
int n=strlen(s+1);
f[n+1][0][0]=f[n+1][0][2]=f[n+1][1][0]=f[n+1][1][2]=node(1,0);//序列可以同时不存在u,v。
fd(i,n,1){
int c=s[i]-'0';
fo(j,0,1){
int xr=!j?c:1;
fo(k,0,2)//k=0:u,v k=1:v k=2:
fo(x,0,xr){
int yl=k==1?x:0,yr=!k?x:1;
fo(y,yl,yr){
int t=x-y+(k>0),p=j|(x<c),zt;//t为这一位的距离贡献
if(!k) zt=1|(x>y?2:0);//k=0,这一位x>y时由1转移,否则由0转移。
else zt=k&1?(x<y?4:2):4;//k=1,这一位x<y时由2转移,否则由1转移。
fo(l,0,2) if(zt&(1<<l)) f[i][j][k].up(f[i+1][p][l],t);
}
}
}
}
printf("%d",f[1][0][0].g);
}