題目鏈接
題意 你記錄了[0, 59]這個時間段內到達你所在站牌的所有公交的到這個站牌的時間 對於每路公交
-
同一路公交的到站時間間隔是相同的
-
每路公交在這個時間段至少到達兩次
-
最多有17路公交
-
兩個不同路的公交的第一次到站時間和到站時間間隔都可能是相同滴
-
你在這個時間段內的記錄是完整的
求最少用多少路公交可以讓你的記錄合法,等價於在給出的200多個序列中,看看最少有多少個等差數列能完全覆蓋所有的值,
由於每路公交至少到站兩次 那麼第一次到站時間是肯定小於30的 而且到站時間間隔肯定要大於第一次到站的時間 那麼可以根據你的記錄生成所有可能合法的公交線路 最後dfs找出最少的公交線路 使這些線路剛好完全覆蓋你的記錄 注意dfs過程中的剪枝
比較重要的一個剪枝是將所有的合法公交線路排序,覆蓋點數最多的最先開始搜索,此時如果我們還剩個點沒有覆蓋,使用了個公交,當前的序列可以覆蓋個元素,那麼至少還需要個新的公交點,如果的值比當前結果值還大,可以直接跳出。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1505;
int cnt[N], n, m, ans;
struct route //定義公交線路結構體
{
int start, interval, times;
route() {}
route(int s, int i, int t): start(s), interval(i), times(t) {}
bool operator< (const route &r) const {
return times > r.times;
}
} r[N];
bool ok(int time, int inter)
{
while(time < 60)
{
if(!cnt[time]) return false;
time += inter;
}
return true;
}
//從第k條線路開始匹配 當前已經匹配num個記錄 用了sum個公交線路
void dfs(int k, int num, int sum)
{
if(num == n)
{
if(sum < ans) ans = sum;
return;
}
for(int i = k; i < m; ++i)
{
if(sum + (n - num) / r[i].times >= ans) return;
//剪枝 r是按趟數從大到小排序的 所以最少還需要(n - num) / r[i].times個線路
if(!ok(r[i].start, r[i].interval)) continue;
for(int j = r[i].start; j < 60; j += r[i].interval) --cnt[j];
dfs(i, num + r[i].times, sum + 1);
//i之前的線路之前已經搜索過了
for(int j = r[i].start; j < 60; j += r[i].interval) ++cnt[j]; //回溯
}
}
int main()
{
int t;
while(~scanf("%d", &n))
{
memset(cnt, 0, sizeof(cnt));
for(int i = 0; i < n; ++i)
scanf("%d", &t), ++cnt[t]; //記錄每個時刻出現多少公交
m = 0;//生成以時刻i爲首班 兩班間隔時間爲j的所有滿足的公交線路
for(int i = 0; i < 30; ++i)
for(int j = i + 1; j < 60 - i; ++j)
if(ok(i, j)) r[m++] = route(i, j, 1 + (59 - i) / j);
sort(r, r + m);
ans = 17;
dfs(0, 0, 0);
printf("%d\n", ans);
}
return 0;
}