题解 P7751 [COCI2013-2014#2] PUTNIK
洛谷。
题意
一共有 $n$ 个星球,有一个推销员,他有一个要求:对于编号为 $1\sim n$ 的星球,在他登上编号为 $K$ 的星球时,要么那些编号小于 $K$ 的星球都已经被经过过了,要么编号小于等于 $K$ 的星球都没有被经过过。每两个星球之间都有一个飞行时间,计算出经过所有星球最小所需的飞行时间。所有星球必须且只能被经过一次(起点任意)。
分析
这道题我们首先要分析出这个推销员的经过路线的性质,搞懂这个之后,其实这道题是比较简单的。
在他登上编号为 $K$ 的星球时,要么那些编号小于 $K$ 的星球都已经被经过过了,要么编号小于等于 $K$ 的星球都没有被经过过。
假如说我们现在的节点是 $i$,
- $1\sim i$ 全部已经被经过过,那下一步就会是大于 $i$ 的且没有被经过过的最小点;
- 否则,可以再 $1\sim i-1$ 中任选一个经过。
总结一下这个过程,我们的推销员,他会从某个起点开始,不断向下标更小的节点,知道到达 $1$,然后再一步一步向上(不重复经过)。
先考虑暴力,我们暴力模拟我们的上述过程。枚举起点。
inline void dfs(int now,int opt,int tot) {
if(now==n+1) return void (ans=min(ans,tot));
if(now==1) opt=1;
if(opt==1) {
for(int i=now+1; i<=n+1; ++i) {
if(vis[i]) continue;
vis[i]=1;
dfs(i,1,tot+a[i][now]);
vis[i]=0;
break;
}
} else {
for(int i=1; i<now; ++i) {
if(vis[i]) continue;
vis[i]=1;
dfs(i,-1,tot+a[i][now]);
vis[i]=0;
}
}
}
时间复杂度:$O(n\times w^{n})$($w=2$)。
拿下 $60pts$。
可以发现,我们在跑至 $1$ 后的轨迹都是确定的,可以优化这一部分。
当我们在 $opt=-1$ 时枚举下一节点时,考虑我们 $now-1$ 的归属,这个节点要么当做此时的下一个节点,否则,就是在回程的一个节点。
依旧是 dfs,传两个参数 $l$,$r$,$l$ 表示从大到小过程中的上一个节点,$r$ 表示从小到大的枚举到的第一个节点,如此,我们下一个枚举的节点就是 $min(l,r)-1$。
要么是接在从大到小的最后面,要么是接在从小到大的最前面。
并且由于我们枚举了起点,由此,我们需要先处理完 $i$ 后方的总和。
由此写出代码:
inline int dfs(int l,int r) {
int now=min(l,r);
if(now==1) return a[l][r];
return min(dfs(l,now-1)+a[now-1][r],dfs(now-1,r)+a[now-1][l]);
}
时间复杂度并没有太多变化,依旧是 $60pts$。
但是这个 dfs 好优化啊,显然,我们用记忆化优化。
时间复杂度就变成了:$O(n^2)$。
inline int dfs(int l,int r) {
int now=min(l,r);
if(f[l][r]<INF) return f[l][r];
if(now==1) return a[l][r];
return f[l][r]=min(dfs(l,now-1)+a[now-1][r],dfs(now-1,r)+a[now-1][l]);
}
signed main() {
n=read();
for(int i=1; i<=n ; ++i) for(int j=1; j<=n; ++j) a[i][j]=read();
for(int i=1; i<=n; ++i) for(int j=i+1; j<=n; ++j) e[i][j]=e[i][j-1]+a[j][j-1];
memset(f,0x3f,sizeof f);
int ans=INF;
for(int i=n; i; --i) ans=min(ans,e[i+1][n]+dfs(i,i+1));
cout<<ans;
return 0;
}
如此,就可以愉快的 A 掉此题了,其实代码并不难写,方向在理解好题目后其实也是比较显然的。

浙公网安备 33010602011771号