【题解】2019,7.14 模拟赛(阿鲁巴)
Description
就是给出一个数 \(k\),再给出一个边长为 \(n=2^{k}\) 的矩阵每个元素是 \(w_{i,j}\),然后要求我们求出一个 \([0,n-1]\) 序列 \(p\) 使 他的\(\sum_{i=1}^{n-1} w_{p_{i-1},p_i}\) 最小,同时这个序列要满足对于 一个小于等于 \(k\) 的 \(j\) 满足分成 \(2^{k-j}\) 块,并且这些块内的 \(p_i\)的第 \(j\) 位都相同。
Solution
前两天模拟赛做到一道特别厉害的题,来的分享想一下
其实这题是司机出的T3,照理说应该是没人切得动的,但是“大海”不但切了,还把司机的标算吊打了一发。
标算复杂度: \(O(N^3)\)
大海复杂度: \(O(N^2k)\)
\(k\) 的范围和 \(log\) 同阶。
那么我们还研究司机的算法搞个鸡毛,看“大海”啊。
我们先拿样例来找找规律:
1 0 3 2
我们把他二进制分解:
01 00 11 10
我们发现分个组:
01 00 | 11 10
他们的第一位的数是不同的(左边是0,右边是1)。
这个根据题目要求其实可以简单发现。
因为前一段是第一位是0,如果后一段和前一段的第一位相同的话就会重复,同时之后还要保证他们的第2位相同那么就会重复,那就非常尴尬了(我连我自己)。
所以要从最小的长度 \(2\) 开始,保证这种尴尬情况不出现。
所以我们发现一个确定下一个数填什么的一个方法,但是具体怎么确定把那一位取个反让他们不同捏?
我们对他的编号观察一下:
0 1 2 3
我们这样看不出什么鬼东西,我们对他二进制分解一下:
00 01 10 11
欧,好像有点东西?我们发现好像在编号为 \(2\) 的时候发生了取反,并且取反位置好像和这个东西的后面有几个 \(0\) 有关系,不错,好像有点进展。\(0\) 根 \(lowbit\) 有点关系。
但是我们还是不知道确定下一位那个位取反之后怎么知道后一位到底会是多少。
我们动了动脑yy了一下,好像又有点眉目了?
好像不能直接求出下一个,因为要取 \(min\) 所以这就很显然了,我们得枚举。
但是直接枚举的话好像对我们刚才得出的信息没什么用,我们可以利用一下题目,如果我们已经确定了前一位是啥,那不是我们对想应位置取反之后就可以推出这一位序列里选什么。
但是还是要枚举那些前面相同那些从 \(lowbit\) 开始不一样的那些编号。
那么我们基本确定了一个方案:
设 \(f[k][num]\) 表示前 \(k\) 个,上一个填了 \(num\) 的最小序列值。
那么我们大力枚举下一个数取反后 \(lowbi\) 后面有几个 \(1\) ,随便一坨转移一下就好了。。。
滑稽。。。其实代码还是很好理解的:
#include<bits/stdc++.h>
using namespace std;
int BIT,n;
const int N=(1<<9)+5,K=10,INF=1E9;
int a[N][N],f[N][N];
// f[i][j]表示前k-1位已经放好,上一位编号是j的最小花费
inline int dfs(int k,int num){
if(k==n) return 0;
if(f[k][num]!=-1) return f[k][num];
int bit=0,p=k;
while(bit<BIT && !(p&1)){
bit++;
p>>=1;
}
int tmp=(1<<bit);
int tmp_r=(((num^tmp)>>bit)<<bit);
f[k][num]=INF;
for(int i=0;i<tmp;++i)
f[k][num]=min(f[k][num],a[num][tmp_r+i]+dfs(k+1,tmp_r+i));
return f[k][num];
}
int main(){
memset(f,-1,sizeof(f));
scanf("%d",&BIT);
n=(1<<BIT);
for(int i=0;i<n;++i) for(int j=0;j<n;++j)
scanf("%d",&a[i][j]);
printf("%d\n",dfs(0,n));
// 0,n方便处理
return 0;
}