给出序列a和b。
一次操作可以选择一个k,把当前序列中的某些数模k。
求把a变为b的最少操作次数。
1≤n≤50; 0≤a,b≤50.
(想要直接特判的话)无解: 2 b i + 1 > a i      a n d      b i ≠ a i \frak{2b_i+1>a_i\;\;and\;\;b_i\ne a_i} 2bi+1>aiandbi̸=ai
数据范围允许复杂度达到
Θ
(
n
4
)
\frak{\Theta(n^4)}
Θ(n4)。
考虑暴力。
题目的要求相当于转移使初状态变为末状态。可以通过mod 1~50来转移。
由于mod运算的性质,取模顺序不影响结果。可以构造一个n x n的矩阵来转移。
预处理1~50的每个数mod 1~50的结果填进表格里面。
于是可以用dijkstra跑单源最短路。转移一次的时间复杂度为
Θ
(
n
!
)
\frak{\Theta(n!)}
Θ(n!)。
状态数大约n!,每个点有n条边。
总的时间复杂度大约是
Θ
(
  
(
n
!
)
2
n
l
o
g
(
n
!
)
  
)
\frak{\Theta(\;(n!)^2nlog(n!)\;)}
Θ((n!)2nlog(n!))?
总之这肯定承受不来。
重新考虑。之前的算法复杂度爆炸的重要原因就是状态过多,其中很多状态无效。
事实上不应该整个序列放一起考虑,这样是不优的。
因为这样要考虑每次要操作哪些数,而且数放一起转移就变成了不同的数都影响整体、转移会被大大拖累。
注意到数转移的过程是独立的。尝试考虑局部。
可以这么转化:对于a序列里面的每一个数,它能否转移到b序列里面对应的数?
这样的话重复状态会大大减少。
所以可以求出所有能让
a
i
→
b
i
\frak{a_i\to b_i}
ai→bi可行的操作方案,并且要在里面找到最小代价。
当然操作方案也不能整体来,
2
n
\frak{2^n}
2n的复杂度也不是讲着玩的
怎么独立考虑?
⋮
\vdots
⋮
好像也不好独立考虑然后一步到位,能不能拆成两部分呢?
先判可行再判最小?
?
考虑“可行”和“最小”。
可行:用不大于k的数能否让a转移到b可行
最小:用不大于k的数让a转移到b的最小代价?
首先对于每个k,floyd判连通。
Θ
(
n
4
)
\frak{\Theta(n^4)}
Θ(n4)。
最小怎么判断?很容易看出不是二分就是dp。不过好像二分不了,那就用dp吧。
如果设
f
(
i
,
j
,
k
)
\frak{f(i,j,k)}
f(i,j,k)表示对于数i,前j个数让它剩下k的最小代价?
如果
f
(
i
,
j
,
k
)
\frak{f(i,j,k)}
f(i,j,k)不是inf,向后转移:
初始化
f
(
i
,
j
+
1
,
k
)
=
f
(
i
,
j
,
k
)
\frak{f(i,j+1,k)=f(i,j,k)}
f(i,j+1,k)=f(i,j,k)。(如果前面j-1个可以,实际上没必要选j,选j代价一定只会变大。不过想搞个取min也随意啦)
更新:
f
(
i
,
j
+
1
,
k
%
(
j
+
1
)
)
=
m
i
n
{
f
(
i
,
j
+
1
,
k
%
(
j
+
1
)
)
,
f
(
i
,
j
,
k
)
+
2
j
}
\frak{f(i,j+1,k\%(j+1))=min\{f(i,j+1,k\%(j+1)),f(i,j,k)+2^j\}}
f(i,j+1,k%(j+1))=min{f(i,j+1,k%(j+1)),f(i,j,k)+2j}
但是如果
f
(
i
,
j
+
1
,
k
%
(
j
+
1
)
\frak{f(i,j+1,k\%(j+1)}
f(i,j+1,k%(j+1)已经可行的话好像也没有必要转移?因为
2
j
>
∑
i
=
1
j
2
i
2^j>\sum\limits_{i=1}^j2^i
2j>i=1∑j2i
那好像可以改一改定义了。而且方程里面i也一直没有动过。
枚举不同的
a
i
\frak{a_i}
ai。
设
f
(
k
,
j
)
\frak{f(k,j)}
f(k,j)表示前k个数能不能让
a
i
\frak{a_i}
ai剩下j。
直接转移可行性。
咦这好像跟floyd没关系了啊
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<ctime>
#include<cmath>
using namespace std;
int N;
int A[55]={};
int B[55]={};
int R[55]={};
long long E[55]={};
bool F[55][55]={};
long long ans=0;
bool check(int x)
{
for(int i=1;i<=N;++i)
{
memset(F,0,sizeof(F));
F[1][A[i]]=1;
for(int j=1;j<=R[0];++j)
{
for(int k=0;k<=50;++k)
{
if(F[j][k])
{
F[j+1][k]=1;
F[j+1][k%R[j]]=1;
}
}
}
if(!F[R[0]+1][B[i]])return 0;
}
return 1;
}
int main()
{
scanf("%d",&N);
for(int i=1;i<=N;++i)
{
scanf("%d",&A[i]);
}
for(int i=1;i<=N;++i)
{
scanf("%d",&B[i]);
if(B[i]+B[i]+1>A[i]&&B[i]!=A[i])
{
printf("-1");
return 0;
}
}
for(int i=50;i>=1;--i)
{
R[0]=0;
for(int j=1;j<=E[0];++j)R[++R[0]]=E[j];
for(int j=i-1;j>=1;--j)R[++R[0]]=j;
if(!check(i))E[++E[0]]=1ll*i;
}
for(int i=1;i<=E[0];++i)
{
ans+=1ll<<E[i];
}
printf("%lld",ans);
return 0;
}
另一种方式更简单,建图,从大到小每次删掉一个模数判可行,如果从可行变成不可行了就加回去,如果还是可行就去掉。
判可行可以搜索。 上面提到的floyd也可以。
代码不写了(
实际上不用想得这么复杂。最基础的部分推出来然后可以直接猜状态:
对于某个数,已经从大到小考虑过前几种方案,能不能让余数是某个数。就完了。