Algorithm 动态规划对更大状态的依赖性

Algorithm 动态规划对更大状态的依赖性,algorithm,dynamic-programming,Algorithm,Dynamic Programming,有N个城市。每个城市都有一个类型为1到10(含)的传送站。您将获得一个大小为N的数组,该数组用整数表示每个城市的传送类型。例如1 3 5 3 8 9。目标是在给定以下规则的情况下,找到从数组中的第一个条目到最后一个条目所需的最小小时数: 从每个城市(数组中的条目)可以移动到它的左或右邻居(如果有),该操作需要1小时 从每个城市你都可以传送到另一个城市,传送类型与你传送的城市相同。在上面的示例数组中,您可以从索引1处的城市传送到索引3处的城市,因为它们都具有相同类型的传送:3。此操作将花费给定的R

有N个城市。每个城市都有一个类型为1到10(含)的传送站。您将获得一个大小为N的数组,该数组用整数表示每个城市的传送类型。例如
1 3 5 3 8 9
。目标是在给定以下规则的情况下,找到从数组中的第一个条目到最后一个条目所需的最小小时数:

  • 从每个城市(数组中的条目)可以移动到它的左或右邻居(如果有),该操作需要1小时
  • 从每个城市你都可以传送到另一个城市,传送类型与你传送的城市相同。在上面的示例数组中,您可以从索引1处的城市传送到索引3处的城市,因为它们都具有相同类型的传送:
    3
    。此操作将花费给定的R小时数
  • 我已经实现了一个动态规划解决方案,当最快的方法只是向前推进时,它的效果非常好。但在某些情况下,返回几个城市进行远程传送会更快,从而最大限度地减少花费的时间

    这是我的算法失败的一个例子:例如
    213466857423551234567
    R=2
    正确答案:索引0(时间=0)->索引9(时间=2)->索引8(时间=3)->索引7(时间=4)->索引19(时间=6)。 我的算法只会找到向前移动的最快方式,而正确的最快方式显然也包括向后移动

    以下是我目前的代码:

    #include <iostream>
    using namespace std;
    
    int main()
    {
        int dp[200] = {};
        short ti[200] = {};
        int numCities, R;
        cin >> numCities >> R;
    
        for (int i = 0; i < numCities; i++)
        {
            short temp;
            cin >> temp;
            ti[i] = temp;
        }
    
        for (int i = 1; i < numCities; i++)
        {
            dp[i] = dp[i - 1] + 1;
    
            for (int x = i - 1; x >= 0; x--)
            {
                if (ti[x] == ti[i])
                {
                    if (R + dp[x] < dp[i])
                         dp[i] = R + dp[x];
                }
            }
        }
    
        cout << dp[numCities - 1];
    
        return 0;
    }
    
    #包括
    使用名称空间std;
    int main()
    {
    int-dp[200]={};
    短ti[200]={};
    国际货币基金组织,R;
    中国>>城市>>R;
    对于(int i=0;i>温度;
    ti[i]=温度;
    }
    对于(int i=1;i=0;x--)
    {
    if(ti[x]==ti[i])
    {
    if(R+dp[x]cout动态规划适用于问题具有最优子结构的情况。也就是说,您必须找到一种方法来细分问题,以便将细分的最佳解决方案用作构建块,以找到整个问题的最佳解决方案

    上面,我看到你说你想使用动态规划。我看到了代码。我没有看到的是对你正在考虑的子问题的清晰解释。也就是说:对解决方案的概念性理解是正确使用动态规划的关键,而这正是你没有提供的

    我的直觉是,在这种情况下,动态规划不是一种好方法,因为:

    • 重新排列数组中的条目会破坏有关局部移动的信息
    • 从一开始,就有可能移动到数组中的任何条目—一种固有的非局部性
    您可以通过使用嵌套循环来处理这些问题。这将为您提供一个O(n^2)时间解决方案

    但是,将此问题视为加权图遍历的一个实例,允许您使用O(n logn+m)时间(O(n)遍历足以建立每个节点的邻居)来解决此问题,其中m是所考虑的边数(这里可以将此限制为Θ(m)的值)通过认识到每种传送类型只能使用一次)为什么不这样做

    您可以尝试通过使用来改进运行时间,尽管我不相信这会在一个维度上提供很多改进

    完成此操作的代码可能如下所示:

    #include <iostream>
    #include <queue>
    #include <unordered_set>
    #include <unordered_map>
    
    typedef std::unordered_map<int, std::vector<int> > tele_network_t;
    
    int Dijkstra(const std::vector<int> &graph, const tele_network_t &tn, const int R){
      //This whole mess makes the smallest elements pop off the priority queue first
      std::priority_queue<
        std::pair<int, int>,
        std::vector< std::pair<int, int> >,
        std::greater< std::pair<int, int> >
      > pq; //<distance, index>
    
      //Keeping track of the teleporters used allows us to speed up the algorithm by
      //making use of the theorem that each teleporter type will be used only once.
      std::unordered_set<int> teleporters_used; 
    
      //Keep track of the path
      std::vector<int> parent(graph.size(),-1); //Parent==-1 indicates an unvisited node
    
      //At 0 distance, place the 0th node
      pq.emplace(0,0);
      parent[0] = 0; //The only node whose parent is itself should be node 0
    
      while(!pq.empty()){
        const auto c = pq.top();
        pq.pop();
    
        //We've reached the goal node
        if(c.second==graph.size()-1){
          std::cout<<"Dist = "<<c.first<<std::endl;
          break;
        }
    
        //Insert neighbours
        if(c.second!=0 && parent[c.second-1]==-1){ //Left neighbour
          parent[c.second-1] = c.second;
          pq.emplace(c.first+1,c.second-1);
        }
        if(parent[c.second+1]==-1){ //Right neighbour: can't overflow because goal is the rightmost node
          parent[c.second+1] = c.second;
          pq.emplace(c.first+1,c.second+1);
        }
    
        //Inner loop is executed once per teleporter type
        if(teleporters_used.count(graph[c.second])==0)
          for(const auto i: tn.at(graph[c.second])){
            if(parent[i]==-1){
              pq.emplace(c.first+R,i);
              parent[i] = c.second;
            }
          }
    
        teleporters_used.insert(graph[c.second]);
      }
    
      //Trace our steps backwards to recover the path. Path will be reversed, but a
      //stack could be used to fit this.
      int p = graph.size()-1;
      while(parent[p]!=p){
        std::cout<<p<<std::endl;
        p = parent[p];
      }
      std::cout<<0<<std::endl;
    }
    
    int main(){
      tele_network_t tele_network;
    
      const int R = 2;
      std::vector<int> graph = {{2,1,3,4,6,8,5,7,4,2,3,5,2,1,3,4,3,5,6,7}};
    
      //Determine network of teleporters
      for(int i=0;i<graph.size();i++)
        tele_network[graph[i]].push_back(i);
    
      Dijkstra(graph, tele_network, 2);
    }
    
    #包括
    #包括
    #包括
    #包括
    typedef std::无序地图远程网络;
    int Dijkstra(const std::矢量和图形、const tele_network_t&tn、const int R){
    //整个混乱使得最小的元素首先从优先级队列中弹出
    std::优先级队列<
    std::pair,
    std::vector,
    标准::更大<标准::对>
    >pq//
    //跟踪所使用的传送机可以让我们通过
    //利用每种传送类型只使用一次的定理。
    std::使用无序的远程传送装置;
    //跟踪路径
    std::vector parent(graph.size(),-1);//parent==-1表示未访问的节点
    //在距离为0时,放置第0个节点
    pq.安放位置(0,0);
    父级[0]=0;//父级为自身的唯一节点应为节点0
    而(!pq.empty()){
    const auto c=pq.top();
    pq.pop();
    //我们已经到达目标节点
    if(c.second==graph.size()-1){
    
    std::coutMy天真的方法可以用来解决这个问题。DP可能在这里起作用,但我个人看不到一种方法。根据我的回答,我认为你可能误解了数据。它被安排为一个数组,但如果你把所有东西都连接在一起,你会看到它实际上更像一个云。DP在你可以利用数据的线性(或2D)排列,或者你可以将数据视为一棵树。较低/较高的状态可能指的是这棵树。阅读关于可能会有所帮助。DP本质上是展开调用图记忆利用,以完全避免递归调用。为什么Dijkstra O(n log n)在这里?一般来说,Dijkstra是O(n+m)这里m是θ(n^2),因为至少有n/10个城市共享至少一种传送类型。我犯了一个小错误——Dijkstra通常是O(n logn+m),而不是O(n+m)。但重点仍然是——这是O(n^2)因为给出的理由above@PaulHankin:这是因为m只测量考虑的边的数量。
    2…
    的输入名义上有O(n^2)条边,但只需要考虑O(n),因为第一次使用传送机有效地删除了O(n^2-n)我在上面添加了代码,并标记了允许您跳过的内部循环。