BC Round#86 D(dp,设置序)

这是一道具有挑战性的动态规划(dp)问题,描述了在Byteland的特工团队如何在限定无线电频差值内进行秘密任务。题目要求计算在每次任务中,特工3人从不同城市出发,有多少种合法的行动方案,确保他们在任何时刻都能两两联络。关键在于利用城市编号顺序简化问题,通过n^4复杂度的dp方案解决原本n^6的难题。

感觉这是一道很不错的dp呢,比赛的时候想了许久也没做出来,然后一开始过了七八十人后来好多人都被hack(因为都是用暴力水),最后只过了22个,所以可见这道题有较大的思维难度额。

上题:在Byteland一共有n个城市,编号依次为1到n,同时有m条单向道路连接着这些城市,其中第ii条道路的起点为ui,终点为vi(这里有个关键条件,就是每条路都是起点比终点小,这样就不会出现环了,而且后面也不需要用toposort了)
特工团队一共有33名成员:007,008,以及009,他们将要执行qq次秘密任务。
在每次任务中,三人可能会处于三个不同的城市,他们互相之间通过对讲机保持联络。编号为ii的城市的无线电频为wi,如果两个城市的无线电频差值的绝对值不超过K,那么无线电就可以接通。三个特工每个时刻必须要选择一条道路,走到下一个城市,每条道路都只需要花费1单位时间。
他们可以选择在任意城市终止任务,甚至可以在起点就终止任务,但不允许在道路上终止任务。现在他们想知道,对于每次任务,给定三个人的起始位置,有多少种可能的合法行动方案,使得行动过程中任意在城市的时刻,他们都可以两两联络?
两个方案被视作不同当且仅当至少存在一个人在某一时刻所在的城市不同。
注意:33个特工必须同时结束任务。

一开始的思路:很容易就想到了n^6的暴力再toposort,显然不行,悲伤。后来看了clarification,发现了那个很重要的条件,但还是要n^6,直到比赛结束也没想出来。

题解:多开一维,dp[i][j][k][l],最后一维纪录该谁走,0是007走,1是008走,2是009走,这样转移的时候就需要在009走完,转移到007走的时候判断一下这三个点是否可以通信就可以了,复杂度大大降低(主要是设置了一个序,来使得操作有序,这样三个嵌套的循环就变成并列的了,复杂度也变成n^4了)。详细见代码。

//
//  main.cpp
//  D
//
//  Created by 黄宇凡 on 8/6/16.
//  Copyright © 2016 黄宇凡. All rights reserved.
//

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>

using namespace std;

const int maxn = 55;
const int mod = 998244353;

int dp[maxn][maxn][maxn][4];

vector<int> G[maxn];
int w[maxn];

int n;int T;
int m,q,K;

int main(int argc, const char * argv[]) {
    cin >> T;
    while(T--){
        cin >> n >> m >> K >> q;
        for(int i = 1;i <= n;i++) scanf("%d",w + i),G[i].clear();
        for(int i = 1;i <= m;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            G[v].push_back(u);
        }
        for(int i = 1;i <= n;i++){
            for(int j = 1;j <= n;j++){
                for(int k = 1;k <= n;k++){
                    for(int l = 0;l < 4;l++) dp[i][j][k][l] = 0;
                    if(abs(w[i] - w[j]) <= K && abs(w[i] - w[k]) <= K && abs(w[j] - w[k]) <= K) dp[i][j][k][0] = 1;
                }
            }
        }
        for(int i = n;i >= 1;i--){
            for(int j = n;j >= 1;j--){
                for(int k = n;k >= 1;k--){
                    for(int l = 0;l < G[i].size();l++){
                        int v = G[i][l];
                        (dp[v][j][k][1] += dp[i][j][k][0]) %= mod;
                    }
                    for(int l = 0;l < G[j].size();l++){
                        int v = G[j][l];
                        (dp[i][v][k][2] += dp[i][j][k][1]) %= mod;
                    }
                    for(int l = 0;l < G[k].size();l++){
                        int v = G[k][l];
                        if(abs(w[i] - w[j]) <= K && abs(w[v] - w[i]) <= K && abs(w[v] - w[j]) <= K)
                            (dp[i][j][v][0] += dp[i][j][k][2]) %= mod;
                    }
                }
            }
        }
        while(q--){
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            printf("%d\n",dp[x][y][z][0]);
        }
    }
    return 0;
}

很巧妙的一个dp,给人以启发。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值