\(\frak{Description}\)
\(\frak{Solution}\)
只会三十分,考试的时候冲了一发模拟退火 —— \(\rm rnm\),还是三十分!打开题解:"一个显然的结论",焯!
\(\text{Method 0}\)
朴素的想法是枚举信号站的顺序,复杂度 \(\mathcal O(n\cdot m!)\). 事实上,我们可以观察到当 \(n\) 较大时,有 \(m^2\) 远小于 \(n\),所以不妨换一个思路:预处理出 \(i\) 号信号站传递信息给 \(j\) 号信号站的次数 \(\text{cnt}_{i,j}\),这样复杂度就降低到 \(\mathcal O(n+m^2\cdot m!)\).
具体地,令 \(i\) 号信号站的位置为 \(p_i\),那么有单次 \(i\rightarrow j\) 的花费
观察到每一项的系数实际上与 \(p_i,p_j\) 的相对关系有关,我们可以想出这样一个 \(\mathtt{dp}\):令 \(dp_{s}\) 表示考虑前 \(|s|\) 个位置,集合 \(s\) 的信号站放在这些位置上的答案。转移就有
复杂度降低到了 \(\mathcal O(m^2\cdot 2^m)\). 其中 \(p_i\) 就是 \(s\) 中 \(1\) 的个数。
考虑优化,我们尝试扔掉 \(j\) 那一层循环,令 \(g(s,i)\) 为对应 \(s,i\) 的转移加值(忽略 \(p_i\)),考虑经典地用最低位(不妨设 \(s\) 最低位为第 \(j\) 位)转移
现在复杂度就变成了 \(\mathcal O(m\cdot 2^m)\),但是 \(g\) 的空间仍然过大,接下来讨论 \(g\) 的优化方案。
\(\text{Method 1}\)
其实,上面内存算出来 \(\text{736 mb}\),除以二就在可接受的范围内了嘛。由于 \(g(s,i)\) 合法的值一定满足 \(i\in s\),所以只需要开 \(23\cdot 2^{22}\) 的内存,可以卡过这道题。
\(\text{Method 2}\)
我们还可以利用一个性质:将二进制计数器加 \(n\) 次,二进制位变化次数在 \(\mathcal O(n)\) 级别。
所以还可以只开 \(g(i)\),从小到大枚举 \(s\) 的时候顺带计算 \(g(s,i)\). 比如从 \(s-1\) 转移到 \(s\) 就可以遍历 \((s-1)\oplus s\) 的所有为 \(1\) 的位置(也就是变化位),然后加加减减就行了。
\(\frak{Code}\)
\(\text{Method 1}\)
# include <cctype>
# include <cstdio>
# define print(x,y) write(x), putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while(!isdigit(s=getchar())) f|=(s=='-');
for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
return f? -x: x;
}
template <class T>
inline void write(T x) {
static int writ[50], w_tp=0;
if(x<0) putchar('-'), x=-x;
do writ[++w_tp]=x-x/10*10, x/=10; while(x);
while(putchar(writ[w_tp--]^48), w_tp);
}
# include <cstring>
# include <iostream>
using namespace std;
const int maxm = 33;
int n,m,k,c[maxm][maxm];
int dp[1<<23],g[23][1<<22];
int main() {
memset(dp,0x3f,sizeof dp);
n=read(9), m=read(9), k=read(9);
int x, pre, lim=(1<<m), hal=(lim>>1);
for(int i=1; i<=n; ++i, pre=x) {
x=read(9)-1;
if(i^1) ++ c[pre][x];
}
for(int i=0;i<m;++i) {
for(int j=0;j<m;++j) if(i^j)
g[i][0] += c[j][i]*k-c[i][j];
for(int j=1;j<hal;++j) {
int to = __builtin_ctz(j); to+=(to>=i);
g[i][j] = g[i][j^(j&-j)]+c[i][to]*(1+k)+c[to][i]*(1-k);
}
}
dp[0]=0;
for(int s=1;s<lim;++s)
for(int t=s; t; t^=(t&-t)) {
int to = __builtin_ctz(t), bit = (t&-t);
dp[s] = min(dp[s],dp[s^bit]+g[to][(s>>to+1<<to)|(s&bit-1)]*__builtin_popcount(s));
}
print(dp[lim-1],'\n');
return 0;
}
\(\text{Method 2}\)
# include <cctype>
# include <cstdio>
# define print(x,y) write(x), putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while(!isdigit(s=getchar())) f|=(s=='-');
for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
return f? -x: x;
}
template <class T>
inline void write(T x) {
static int writ[50], w_tp=0;
if(x<0) putchar('-'), x=-x;
do writ[++w_tp]=x-x/10*10, x/=10; while(x);
while(putchar(writ[w_tp--]^48), w_tp);
}
# include <cstring>
# include <iostream>
using namespace std;
const int maxm = 33;
int n,m,k,c[maxm][maxm];
int dp[1<<23],g[23];
int main() {
memset(dp,0x3f,sizeof dp);
n=read(9), m=read(9), k=read(9);
int x, pre, lim=(1<<m);
for(int i=1; i<=n; ++i, pre=x) {
x=read(9)-1;
if(i^1) ++ c[pre][x];
}
for(int i=0;i<m;++i) {
for(int j=0;j<m;++j) if(i^j)
g[i] += c[j][i]*k-c[i][j];
}
dp[0]=0;
for(int s=1;s<lim;++s) {
int coe = __builtin_popcount(s);
for(int t=s^(s-1); t; t^=(t&-t)) {
int to = __builtin_ctz(t), bit = (t&-t);
if(bit&(s-1)) {
for(int i=0;i<m;++i) if(i^to) g[i] -= c[i][to]*(1+k)+c[to][i]*(1-k);
} else for(int i=0;i<m;++i) if(i^to) g[i] += c[i][to]*(1+k)+c[to][i]*(1-k);
}
for(int t=s; t; t^=(t&-t)) {
int to = __builtin_ctz(t), bit = (t&-t);
dp[s] = min(dp[s],dp[s^bit]+g[to]*coe);
}
}
print(dp[lim-1],'\n');
return 0;
}