原题链接: https://www.acwing.com/problem/content/188/
题目描述
一名男子在12:00抵达了某巴士站,并且在12:00-12:59期间他将在那里逗留。
巴士站有很多巴士路线,巴士抵达的时间均已给出。
该男子观察巴士的抵达时间,有所发现:
1、在12:00 ~12:59 期间,同一线路上的巴士以相同的时间间隔到站。
2、每条巴士线路至少有两辆车到达本站。
3、不同线路的巴士可以同时到达本站。
4、不同巴士线路的车首次到达本站的时间和到站的时间间隔都有可能相同。
5、测试用例中的总线路不会超过17条。
请你编写一个程序,求出在所有巴士到达本站的时刻满足输入数据的要求的情况下,巴士线路的总数量最小是多少。
输入格式
输入数据第一行包含整数n,表示在这一小时内抵达到该站的巴士总数量。
第二行包含n个整数,表示按升序排序得到的n个巴士的到站时间。
输出格式
输出一个整数,表示最小巴士线路数。
数据范围
1≤n≤300
输入样例:
17
0 3 5 13 13 15 21 26 27 29 37 39 39 45 51 52 53
输出样例:
3
题解
首先预处理出所有可能的线路。先枚举起点i,再枚举公差j,则i和j需要满足两个条件:
由于i是起点,因此0 ~ i - 1中不能包含任何该序列的点,所以公差j至少是i + 1;
由于0 ~ 59之间至少要包含两个点,因此i + j一定小于60;
剩下的问题变成:
最少从合法线路中选出多少条,才可以覆盖所有给定的公交车。
由于总路线数量较多,最多从中选出17条,但实现我们并不知道该选多少条,因此可以采用迭代加深搜索。
剪枝:
1.由于是枚举组合数,并不是排列数,为了避免重复在DFS时传入当前枚举的起点。
2.将所有等差数列按长度排序,优先枚举长度较长的等差数列。这样在搜索树中前几层的分支少,可以更快地发现矛盾然后回溯。
3.由于剪枝2的存在,当前路线覆盖的点数是最多的,如果当前路线能覆盖的点数 * 剩余可选的路径条数 + 当前已经覆盖的点数 < 总点数,说明当前方案一定非法,直接回溯即可。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int M = 60;
int bus[M];
int n;
vector<pair<int, PII>> routes;
bool is_route(int a, int b)
{
for(int i = a; i < 60; i += b)
{
if(!bus[i]) return false;
}
return true;
}
bool dfs(int depth, int u, int sum, int start) //剪枝一
{
if(u == depth) return sum == n;
if(routes[start].first * (depth - u) + sum < n) return false; //剪枝3
for(int i = start; i < routes.size(); i++) //注意:搜索的是路径
{
auto r = routes[i];
int dist = r.first;
int a = r.second.first, d = r.second.second;
if(!is_route(a, d)) continue;
for(int k = a; k < 60; k += d) bus[k]--;
if(dfs(depth, u + 1, sum + dist, i)) return true;
for(int k = a; k < 60; k += d) bus[k]++;
}
return false;
}
int main()
{
cin >> n;
for(int i = 0; i < n; i++)
{
int t;
scanf("%d", &t);
bus[t]++;
}
for(int i = 0; i < 60; i++) //枚举起点
{
for(int j = i + 1; i + j < 60; j++) //枚举公差
{
if(is_route(i, j))
{
routes.push_back({(59 - i)/ j + 1, {i, j}}); //覆盖点数 + 路线
}
}
}
sort(routes.begin(), routes.end(), greater<pair<int, PII>>()); //剪枝2
int depth = 0;
while(!dfs(depth, 0, 0, 0)) depth++;
cout << depth << endl;
return 0;
}