【JZOJ6103】Diyiti
Description
Solution
考虑两个序列(下标为
1
1
1~
n
n
n,左边为高位):
x
:
1100101
x:1100101
x:1100101
y
:
1001011
y:1001011
y:1001011
u
v
\ \ \ \ \ \ \ u\ \ v
u v
它的距离如何计算?
先找到最小的
v
v
v使得
x
v
<
y
v
x_v<y_v
xv<yv,再找到比
v
v
v小最大的
u
u
u使得
x
u
>
y
u
x_u>y_u
xu>yu,那么最优策略是将
x
i
=
1
(
i
>
u
)
x_i=1(i>u)
xi=1(i>u)全部抹掉,然后使
x
−
1
x-1
x−1,使得
∀
i
>
u
x
i
=
1
\forall_{i>u} x_i=1
∀i>uxi=1,最后把
y
i
=
1
(
i
>
u
)
y_i=1(i>u)
yi=1(i>u)的
x
i
x_i
xi抹掉。
记
c
n
t
x
cnt_x
cntx、
c
n
t
y
cnt_y
cnty分别位
x
x
x、
y
y
y中
1
1
1的个数,距离即为
c
n
t
x
−
c
n
t
y
+
n
−
u
cnt_x-cnt_y+n-u
cntx−cnty+n−u。
有了这个我们就可以dp了,设
f
i
,
j
=
0..1
,
k
=
0..2
f_{i,j=0..1,k=0..2}
fi,j=0..1,k=0..2表示从后往前做到了第
i
i
i位(低位往高位转移),
j
j
j表示不考虑
i
i
i~
n
n
n位时
x
x
x是否小于
y
y
y(
j
=
1
j=1
j=1表示小于),
k
=
2
k=2
k=2表示
u
u
u,
v
v
v都还没出现,
k
=
1
k=1
k=1表示
v
v
v已出现但
u
u
u未出现,
k
=
2
k=2
k=2表示
u
u
u,
v
v
v都已出现。
f
f
f的转移直接考虑状态意义即可。
重要的是距离和的转移,重新考虑
+
n
−
u
+n-u
+n−u的含义,它代表在
k
>
0
k>0
k>0时当前的
i
i
i都会对前面
u
u
u有贡献,所以在这种情况下直接贡献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);
}