繁体   English   中英

无环无向图分配问题

[英]Acyclic undirected graph allocation problem

我们有一个分配问题:每个节点都是一个燃料源,每条边包含许多均匀间隔的灯(将它们视为彼此相距 1 m)。 每个燃料源都可以为紧靠其周围边缘的灯供电(它不能通过其他节点为灯供电)。 燃料源也有一个半径,它可以为周围的灯提供燃料——根据半径(以米为单位),我们可以计算给定燃料源提供的燃料量——例如:半径为 2 的燃料源可以为所有灯提供燃料。其半径内的灯,总共消耗 2 升燃料(燃料使用量为半径的 function)。

请注意,两个节点之间只有一条路径(意味着边数等于节点数 - 1)。

目标是计算燃料源的最佳半径,以便我们在为所有灯加燃料时最大限度地减少燃料使用量。

这是一个示例图

该图的解决方案如下所示 红色椭圆体应该可视化燃料源的半径,下面是相关值的表格:

节点、燃料源 半径、油耗
0 1个
1个 2个
2个 0
3个 2个
4个 0
5个 0

将所有燃料量加起来后,我们得到结果: 5

上述任务的输入如下所示:

6     // 6 nodes (numVertices)
0 1 3 // Node 0 with an edge containing 3 nodes going to node 3 (src dest lights)
1 2 1 // ...
2 3 2
1 4 2
1 5 2

到目前为止,我已经尝试像这样加载我的边缘(请注意,这个解决方案非常糟糕,尤其是我使用指针时):

struct light {
    int count;
};

struct node {
    int d;
    light* l;
};

std::vector<node*>* tree;
int numVertices;

// Do this for all values in the input
void AddEdge(int src, int dest, int lights) {
        light* l = new light{ lights };
        tree[src].push_back(new node{ dest, l });
        tree[dest].push_back(new node{ src, l });
}

然后我通过使用贪婪算法每一步“移除”尽可能多的灯来解决这个问题:

void Solve() {
    int fuel = 0;

    while (true) {
        int maxNode = 0;
        int maxNodeLights = 0;

        for (int A = 0; A < numVertices; A++)
        {
            int lightsOnNode = 0;

            for (node* B : tree[A])
            {
                lightsOnNode += B->l->count;
            }

            if (lightsOnNode > maxNodeLights) {
                maxNodeLights = lightsOnNode;
                maxNode = A;
            }
        }

        if (maxNodeLights > 0) {
            bool addedRange = false;

            for (node* B : tree[maxNode])
            {
                if (B->l->count > 0) {
                    B->l->count--;
                    addedRange = true;
                }
            }

            if (addedRange) {
                fuel++;
            }
        }
        else {
            break;
        }
    }

    std::cout << fuel << '\n';
}

如果我们要解析示例案例中的输入字符串,它将如下所示:

numVertices = 6;

AddEdge(0, 1, 3);
AddEdge(1, 2, 1);
AddEdge(2, 3, 2);
AddEdge(1, 4, 2);
AddEdge(1, 5, 2);

Solve();

这适用于像上面那样的简单图形,但一旦引入更复杂的图形,它就会以小幅度失败,因为如果未来有几个更好的选择,贪婪算法不会向前看。

可以在此处找到一个较长的测试用例。 该图消耗的燃料量为 77481。

新的、失败的测试用例:

1 0 4
2 1 1
3 2 4
4 3 1
5 1 2
6 1 1
7 2 2
8 3 2
9 1 1
10 1 3
11 5 1
12 0 2
13 10 4
14 3 3
15 5 4

(输出:16。正确的 output:17)

1 0 2
2 1 3
3 2 2
4 1 4
5 4 3
6 3 2
7 5 3
8 3 4

(输出:10。正确的 output:11)

1 0 4
2 0 3
3 0 4
4 3 3
5 2 2
6 3 1
7 2 1
8 3 2
9 3 2
10 2 1
11 9 1
12 4 2
13 5 2
14 8 2
15 9 1
16 14 2
17 3 3
18 3 4

(输出:15。正确的 output:16)

算法伪代码:

  • WHILE 灯仍然没有燃料
    • 循环遍历源
      • IF 源只有一个边缘没有燃料灯
        • 将燃料源设置为未燃料边缘另一端的源
        • INCREMENT 燃料源半径与未加燃料的边灯数
        • 在燃料源的边缘上循环
          • IF 边缘燃料灯从燃料源 < 燃料源半径计数
            • SET edge fueled lamp count 从燃料源到燃料源半径

C++ 代码位于https://github.com/JamesBremner/LampLighter

样品 output

Input
0 1 1
1 2 1
2 3 1
3 4 1
0 5 1
5 6 1
6 7 1
7 8 1
0 9 1
9 10 1
10 11 1
11 12 1

Source radii
0 r=0
1 r=1
2 r=0
3 r=1
4 r=0
5 r=1
6 r=0
7 r=1
8 r=0
9 r=1
10 r=0
11 r=1
12 r=0

Total fuel 6

在每个边缘处理不同数量的灯

0 1 3
1 2 1
0 5 3
5 6 1

Source radius
0 r=2
1 r=1
2 r=0
5 r=1
6 r=0

Total fuel 4

另一个测试

lamp ..\test\data11_19_2.txt

Output:

1 0 2
2 1 3
3 2 2
4 1 4
5 4 3
6 3 2
7 5 3
8 3 4

Source radius
1 r=3
0 r=0
2 r=0
3 r=4
4 r=0
5 r=3
6 r=0
7 r=0
8 r=0

Total fuel 10

致谢:该算法基于MarkB的见解,他建议加油应从叶节点开始并“向下”工作

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM