一、题目
二、解法
因为这道题
a
i
a_i
ai很小,考虑状压
d
p
dp
dp。
将每个颜色是否拼接在一起状压进
s
s
s里,设
d
p
[
s
]
dp[s]
dp[s]为到达当前状态的最小步数。
我们来推导
d
p
dp
dp方程,因为交换是相互的,我们规定只能跟左边的元素交换,这时候我们就可以直接把相同的颜色堆在最左边(不能超越其他已经堆在最左边的颜色块)。为什么呢,因为如果不把相同的颜色堆在最左边,中间总会留下空隙(孤立的颜色),不难发现,直接把所有的颜色堆在最左边可以避免空隙的情况,一定更优。
所以顺手写出
d
p
dp
dp方程:
d
p
[
i
]
=
min
{
d
p
[
j
]
+
c
o
s
t
,
j
∈
i
}
dp[i]=\min\{dp[j]+cost,j\in i\}
dp[i]=min{dp[j]+cost,j∈i}
发现难点在于求
c
o
s
t
cost
cost,我们考虑把用来转移的颜色
k
k
k移动到最左边,设
c
[
i
]
[
j
]
c[i][j]
c[i][j]为颜色
i
i
i要越过多少颜色
j
j
j,则:
c
o
s
t
=
∑
j
=
1
20
[
j
∉
i
]
×
c
[
k
]
[
j
]
cost=\sum_{j=1}^{20} [j\not \in i] \times c[k][j]
cost=∑j=120[j∈i]×c[k][j]
因为对于某个元素,
d
p
dp
dp过程中会移到最左边的只有在它右边的颜色,而我们不会把它计入
c
o
s
t
cost
cost,所以状态不会对
c
c
c数组产生影响,说明了
c
c
c数组是可以预处理的,设
f
[
i
]
[
j
]
f[i][j]
f[i][j]为
i
i
i位置左边有多少
j
j
j颜色,则有:
c
[
i
]
[
j
]
=
∑
x
=
1
n
[
a
[
x
]
=
i
]
×
f
[
x
]
[
j
]
c[i][j]=\sum_{x=1}^{n} [a[x]=i]\times f[x][j]
c[i][j]=∑x=1n[a[x]=i]×f[x][j]
f
[
i
]
[
j
]
=
∑
x
=
1
i
−
1
[
a
[
x
]
=
j
]
f[i][j]=\sum_{x=1}^{i-1} [a[x]=j]
f[i][j]=∑x=1i−1[a[x]=j]
直接把
c
c
c和
f
f
f预处理出来,
c
o
s
t
cost
cost就可以快速算了,时间复杂度
O
(
2
20
×
2
0
2
)
O(2^{20}\times20^{2})
O(220×202)。
(你可以发现这个时间复杂度跟
n
n
n没关系(当然不能很大),所以按我的打法样例都要卡一会才能输出)。
终于口胡完了,虽然作者理解了,但是
w
o
r
d
s
f
a
i
l
e
d
m
e
.
.
.
words\ \ failed\ \ me...
words failed me...
#include <cstdio>
#include <iostream>
#define int long long
using namespace std;
const int MAXN = 400005;
int read()
{
int x=0,flag=1;
char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int n,a[MAXN],f[MAXN][25],cost[25][25],dp[1<<20];
int cal(int s,int i)
{
int res=0;
for(int j=1; j<=20; j++)
if(!(s&(1<<(j-1))))
res+=cost[i][j];
return res;
}
signed main()
{
n=read();
for(int i=1; i<=n; i++)
a[i]=read();
for(int i=2; i<=n; i++)
{
for(int j=1; j<=20; j++)
f[i][j]=f[i-1][j]+(a[i-1]==j);
}
for(int i=1; i<=20; i++)
{
for(int x=1; x<=n; x++)
if(a[x]==i)
{
for(int j=1; j<=20; j++)
cost[i][j]+=f[x][j];
}
}
for(int i=1; i<(1<<20); i++)
{
dp[i]=(1ll<<60);
for(int j=1; j<=20; j++)
if((1<<(j-1))&i)
{
int from=i^(1<<(j-1));
dp[i]=min(dp[i],dp[from]+cal(i,j));
}
}
printf("%lld\n",dp[(1<<20)-1]);
}