- 有(m)个信号站排成一排,相邻两个距离(1)单位长度,在最左边的信号站左边(1)单位长度有个控制塔。
- 现在有一个长度为(n)的传导路径(S),表示第(i)次要将信号从编号(S_i)的信号站传向编号(S_{i+1})的信号站。
- 规定从一个信号站向右传每个单位长度只需(1)点代价;向左传需要先传到控制塔,再由控制塔传到目标信号站,且传每个单位需(k)点代价。
- 你可以任意决定信号站的编号顺序,求最小的总代价。
- (nle10^5,mle23)
贡献转化+子集(DP)
发现(m)这么小,非常容易想到设(f_S)表示从左向右,选择安放了集合(S)中的所有信号站时的最小代价。
这里主要是考虑把(x)向左传到(y)的代价拆成两部分,即从(x)到(y)和(y)到原点一个来回。
其中从(x)到(y)的部分和向右传递是一样的(只是代价不同而已),我们可以把这两部分的代价一起表示到边上。
即,令(g_S)表示一条边左边的点集为(S),右边的点集为(complement S)时,经过这条边的代价总和。也就是说(g_S)就等于(sum_{i=1}^{n-1}[a_iin S,a_{i+1} otin S]+ksum_{i=1}^{n-1}[a_i otin S,a_{i+1}in S])。
具体预处理过程中,我们先求出(p_{u,v})表示传导路径中二元组((u,v))的个数。
然后只要任意找到一个(ein S),从(g_{Soplus e})转移,枚举剩余所有元素,消去(Soplus e)中的元素和(e)之间的贡献,加上(e)和(complement S)中的元素的贡献即可。
还有一部分是(y)到原点一个来回的代价,发现这部分与(x)的具体位置是没有联系的,可以直接在把(y)加入集合的时候计算贡献。
具体地,我们预处理出(c_{y,S})表示集合(S)中所有元素出现在(y)下一位置的总次数,也就是说(c_{y,s}=sum_{i=1}^{n-1}[a_i=y,a_{i+1}in S])。
发现(S)中的元素和(y)计算贡献时是独立的,所以可以任意找到一个(ein S),从(c_{y,Soplus e})转移,加上(y)和(e)之间的贡献(c_{y,e})即可。
这里要注意一个问题,就是(m imes2^m)的数组是开不下的,但实际上我们不需要考虑每个元素在它自己之后的情况,也就是说只需要开一个(m imes2^{m-1})的数组即可,刚好卡在内存上界。
最后的转移就非常简单了,枚举一个不在(S)中的元素(e),得到:
其中(Cnt(S))表示(S)中(1)的个数,而(Cnt(S)+1)就是(e)的位置,也就是它到控制塔的距离。
代码:(O(m2^m))
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define M 23
#define Gmin(x,y) (x>(y)&&(x=(y)))
using namespace std;
int n,m,k,a[N+5],p[M][M],g[1<<M],_c[M][1<<M-1];long long f[1<<M];
I int& c(CI x,CI y) {return _c[x][(y>>x+1<<x)|(y&((1<<x)-1))];}//从状态中除去第x位,卡内存
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
char oc,FI[FS],*FA=FI,*FB=FI;
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
I int Log(RI x) {RI t=0;W(x>>=1) ++t;return t;}I int Cnt(RI x) {RI t=0;W(x) x^=x&-x,++t;return t;}//Log由状态回到编号;Cnt统计1的个数
int main()
{
RI i,j,x,l;for(read(n,m,k),l=(1<<m)-1,i=1;i<=n;++i) read(a[i]),--a[i];//方便起见把编号都减1
for(i=1;i^n;++i) a[i]^a[i+1]&&++p[a[i]][a[i+1]];for(i=2;i<=n;++i) a[i]^a[i-1]&&++c(a[i],1<<a[i-1]);//统计p以及初始的c
for(i=0;i^m;++i) for(j=0;j<=(l>>1);++j) _c[i][j]=_c[i][j^(j&-j)]+_c[i][j&-j];//预处理c,任选一个元素删去后加上贡献
for(i=1;i^l;++i) for(x=Log(i&-i),g[i]=g[i^(1<<x)],j=0;j^m;++j)//预处理g,任选一个元素
x^j&&(i>>j&1?g[i]-=p[j][x]+k*p[x][j]:g[i]+=p[x][j]+k*p[j][x]);//统计把g从S的补集移到S贡献的变化
for(i=1;i<=l;++i) f[i]=1e18;for(i=0;i^l;++i)//子集DP
for(x=Cnt(i)+1,j=0;j^m;++j) !(i>>j&1)&&Gmin(f[i^(1<<j)],f[i]+g[i]+2LL*x*k*c(j,l^i));//枚举一个不在集合中的元素转移
return printf("%lld
",f[l]),0;
}