【CF1310D:爆搜/随机化DP】Tourism

CF1310D:Tourism

【难度】

4.6 / 10 4.6/10 4.6/10
C F 2300 分 CF2300分 CF2300
很难,场上没想出来。。tcl

【题意】

给你一个 n n n 个顶点的完全图,从顶点 i i i j j j 的花费矩阵 c o s t [ i ] [ j ] cost[i][j] cost[i][j]给出。
每个顶点可以访问多次,每条边也可以访问多次。

问你从顶点1出发,经过 k k k 条边后回到顶点1,并且不经过奇数环的最短花费。

【注:】
奇数环这里指:
若对于某个顶点 v v v,若你经过 v v v 后,走了奇数条边又回到了该 v v v ,则是一个奇数环。

【数据范围】

2 ≤ n ≤ 80 2\le n\le 80 2n80
2 ≤ k ≤ 10 , 且 k 为 偶 数 2\le k\le 10,且k为偶数 2k10,k
0 ≤ c o s t [ i ] [ j ] ≤ 1 0 8 0\le cost[i][j]\le 10^8 0cost[i][j]108
Times:3000ms

【样例输入】

N K N\quad K NK
花 费 矩 阵 C O S T [ c o s t [ 1 ] [ 1 ] c o s t [ 1 ] [ 2 ] ⋯ c o s t [ 1 ] [ n ] c o s t [ 2 ] [ 1 ] ⋮ ⋮ ⋱ ⋮ c o s t [ n ] [ 1 ] ⋯ c o s t [ n ] [ n ] ] 花费矩阵COST\left[ \begin{matrix} cost[1][1]&cost[1][2]\cdots &cost[1][n]\\ cost[2][1]&&\vdots\\ \vdots&\ddots&\vdots\\ cost[n][1]&\cdots &cost[n][n] \end{matrix} \right] COSTcost[1][1]cost[2][1]cost[n][1]cost[1][2]cost[1][n]cost[n][n]
5 8
0 1 2 2 0
0 0 1 1 2
0 1 0 0 0
2 1 1 0 0
2 0 1 2 0

【样例输出】

2

【思路】

【做法一:爆搜】

【暴力搜索】
最短花费好搞,只经过 k k k条边也好搞,但是这个奇数环的处理很难搞。

我们使用爆搜的做法:对于每条边,我们每个顶点都遍历一遍。这样会得到一个时间复杂度:
O ( 8 0 10 ) ≈ 1 e 19 O(80^{10})\approx1e19 O(8010)1e19 ,肯定不得行。我们进行剪枝

【感谢三队的 f l o y e d floyed floyed代码与思路提供呀】

我们定义一个三维数组: d i s [ i ] [ j ] [ q ] : 表 示 到 节 点 i 走 到 节 点 j 走 q 条 边 的 最 小 花 费 \color{green}dis[i][j][q]:表示到节点i 走到节点j 走q条边的最小花费 dis[i][j][q]ijq

如果我们中途遍历到 x x x 节点,已经走了 s h u shu shu 条边, d d d 的距离。
目前符合题目要求的最小答案为 a n s ans ans

那么我们若判断出 d + d i s [ x ] [ 1 ] [ k − s h u ] ≥ a n s \color{red}d+dis[x][1][k-shu]\ge ans d+dis[x][1][kshu]ans 则无需继续遍历了。
表 示 接 下 来 所 有 边 无 论 怎 么 选 , 结 果 都 不 会 让 你 比 之 前 的 某 种 方 案 更 优 \color{cyan}表示接下来所有边无论怎么选,结果都不会让你比之前的某种方案更优

【注:】
这里的等于号必须添加,不像一般的最短路加不加都可。否则TLE得不要不要的。。

对于经过某个点是否产生奇环,我们使用一个数组 l a s t [ x ] \color{green}last[x] last[x] 记录上一次走到该顶点时,是走的第几条边。简单判断差值的奇偶性即可。

【核心代码】

dfs剪枝时间复杂度不好算QAQ

Time(ms):62【超快有没有!】

/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
ll cost[MAX][MAX];
ll dis[MAX][MAX][20];
int last[MAX];
int n,k;
ll ans;

void dfs(int x,int shu,ll d){
    if(d + dis[x][1][k-shu]>= ans)return ;		/// 剪枝
    if(shu==k && x==1)ans=min(ans,d);			/// 合法答案取小
    if(shu==k)return ;					/// 选完边就不要选了
    
    for(int i=1;i<=n;++i){
        if(i!=x){
            if(last[i]!=-1 && (shu + 1 - last[i])&1)continue;	/// 奇环不走
            int tmp = last[i];					/// 记录last
            last[i] = shu + 1;					/// 更新last
            dfs(i,shu+1,d+cost[x][i]);
            last[i] = tmp;					/// 回溯last
        }
    }
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i)last[i] = -1;
    
    for(int i=1;i<=n;++i)
    for(int j=1;j<=n;++j)
        scanf("%lld",&cost[i][j]);
        
    for(int p=1;p<=10;++p)					/// dis函数更新
    for(int i=1;i<=n;++i)
    for(int j=1;j<=n;++j){
        if(i!=j || p)dis[i][j][p] = LINF;
        for(int q=1;q<=n;++q)
            if(q!=j)dis[i][j][p] = min(dis[i][j][p],dis[i][q][p-1] + cost[q][j]);
    }
    
    ans = LINF;
    last[1] = 0;
    dfs(1,0,0LL);
    printf("%lld",ans);
    return 0;
}

【做法二:随机化算法+DP】

易得(???)不存在奇环的图为二分图。
但是这个黑白染色(或者左右划分)我们不知道。
若我们暴力枚举,光枚举的时间复杂度: O ( 2 80 ) O(2^{80}) O(280)就炸了!

考虑某条答案链,链长为 k k k ,那么你染色正确(即黑白相间)的概率为 1 2 k − 1 \color{red}\frac{1}{2^{k-1}} 2k11

(解释:)第一种颜色随便染色,后面每种颜色都与前一个颜色不同,每个颜色染色正确的概率为 1 2 \frac{1}{2} 21 k − 1 k-1 k1个全染色正确即为 1 2 k − 1 \frac{1}{2^{k-1}} 2k11

若你随机染色 T T T次, k k k取最大的10,则答案错误的概率为:
( 511 512 ) T \color{red}\Big ( \frac{511}{512}\Big )^T (512511)T

很多人都选择 T T T5000,该值时间正好不超,正确率也比较高。

那么考虑求DP。我们定义如下:
d p [ i ] [ q ] : 表 示 走 了 q 条 边 后 到 节 点 i 的 最 小 花 费 。 \color{green}dp[i][q]:表示走了q条边后到节点i 的最小花费。 dp[i][q]qi
考虑状态转移方程如下:
d p [ i ] [ q ] = min ⁡ n j = 1 ( d p [ i ] [ q ] , d p [ j ] [ q − 1 ] + c o s t [ j ] [ i ] ) , 其 中 i 与 j 的 节 点 染 色 不 同 。 \color{green}dp[i][q]=\underset{j=1}{\overset{n}{\min}}(dp[i][q],dp[j][q-1]+cost[j][i]),其中i与j的节点染色不同。 dp[i][q]=j=1minn(dp[i][q],dp[j][q1]+cost[j][i]),ij
(解释:)
i i i j j j 的颜色不同才可以从节点 j j j 走到节点 i i i
此时:边次数增加1,路程花费增加 c o s t [ j ] [ i ] cost[j][i] cost[j][i]

【核心代码】

时间复杂度: O ( T × N 2 × K ) O(T\times N^2\times K) O(T×N2×K)
Time(ms):1560

/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
ll cost[MAX][MAX];
int zu[MAX];
ll dp[MAX][15];
int n,k;

int main()
{
    srand(time(0));
    scanf("%d%d",&n,&k);
    
    for(int i=1;i<=n;++i)
    for(int j=1;j<=n;++j)
        scanf("%lld",&cost[i][j]);
        
    ll ans = INF;
    ll Tim = 5000;
    while(Tim--){
        for(int i=1;i<=n;++i)zu[i] = rand()%2;		/// 随机染色
        memset(dp,0x3f,sizeof(dp));
        dp[1][0] = 0;
        for(int q=1;q<=k;++q)
        for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            if(zu[i]!=zu[j])dp[i][q] = min(dp[i][q],dp[j][q-1] + cost[j][i]);
        ans = min(ans,dp[1][k]);
    }
    printf("%lld",ans);
    return 0;
}

你可能感兴趣的:(算法,动态规划,随机化算法)