Problem
Preface
我是真的想 bb 两句了,,,这个题目超级简单,但是因为 看错题目x2 导致影响我一天的心情。。。
当然我要把好的题解和心情带给你们呀 qwq!
Solution
首先注意:这题的公交线路是一个严格在 $0-59$ 范围内的等差数列,比如 $3,6,9,12$ 就是一个不合法的公交路线,而 $3,6,9,…,57$ 就是一个合法的公交路线
完全读懂题目后,我们可以很快想到解题思路————
- $1.$ 预处理出来所有合法的公交路线,每个公交路线是一个等差数列,用二元组 $(x,d)$ 来表示,$x$ 是首项,$d$ 是公差,说明这个路线的公交车到站时刻分布在 $x+d,x+2d,…,x+kd$($k$ 是满足 $x+kd<60$ 的最大非负整数)这些时刻。
预处理这里有一个 细节: 注意 $3,6,9,…,57$ 和 $6,9,…,57$,后者甚至不是一个合法的公交路线。我们要避免枚举这种情况,就要保证 $x-d<0 => d>x$ (这样就保证了 $x-d$ 不在 $0-59$,没有更小的起点)
-
$2.$ Dfs实现组合型枚举 + IDfs,我们枚举哪些公交路线叠加起来可以刚好填满所有的时刻。
-
$3.$ 剪枝优化:
1. 喜闻乐见 的优化枚举顺序:我们要尽可能地在每一次枚举公交路线的时候多影响一些点,那么我们可以按每一条公交路线影响的点从大到小排序
2. 附加剪枝(很有用):如果我们还可以枚举 $k$ 个公交路线,又因为我们已经把公交路线从大到小排序,假设当前我们从 $now$ 开始枚举,$now$ 这条公交路线有 $m$ 个点,当前已经影响了 $s$ 个点,总共有 $n$ 个点,如果 $s+k*m < n$ 的话,那么直接 return
(因为最多还能影响 $k*m$ 个点,加起来都还不够的话,这条分支就不能成功)
我不会分析时间复杂度qwq,本来是组合型枚举的Dfs复杂度,加上剪枝后就跑得飞快了 %>_<%
Code
Talk is cheap.Show me the code.
#include<bits/stdc++.h>
using namespace std;
inline int read() {
int x=0,f=1; char ch=getchar();
while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
return x * f;
}
const int N = 67, MAXN = 3607;
int n,cnt,idt;
int bus[N];
struct Node {
int x,d,nums;
bool operator < (const Node &el) const {
return nums > el.nums; //经典剪枝:优化搜索顺序,从大到小搜
}
}route[MAXN]; //route 线路
inline bool check(int x,int d) {
for(int i=x;i<60;i+=d)
if(!bus[i]) return false;
return true;
}
bool Dfs(int dep,int now,int res) {
if(dep == idt) return (res == n);
if(res+(idt-dep)*route[now].nums < n) return false; //在线剪枝
for(int i=now;i<=cnt;++i) {
int x = route[i].x, d = route[i].d, nums = route[i].nums;
if(check(x,d)) {
for(int j=x;j<60;j+=d) bus[j]--;
if(Dfs(dep+1,i,res+nums)) return true;
for(int j=x;j<60;j+=d) bus[j]++;
}
}
return false;
}
int main()
{
n = read();
for(int i=1,pos;i<=n;++i) {
pos = read(); ++bus[pos];
}
for(int i=0;i<60;++i)
for(int j=i+1;i+j<60;++j)
if(check(i,j)) route[++cnt] = (Node)<%i,j,(59-i)/j+1%>;
//printf("test cnt = %d\n",cnt);
sort(route+1, route+1+cnt);
idt = 0;
while(!Dfs(0,1,0)) {
++idt;
if(idt > 17) {
puts("Unkown Problems!");
return 0;
}
}
printf("%d\n",idt);
return 0;
}
Summary
认真看题,认真分析,认真思考!!!
-
组合型枚举模型 Get
-
等差数列 Get
-
优化搜索顺序大法 Get
$M_{ar^k} D^{ow_n}$ 出错了
我记得乘号好像是
\times
预处理这里有一个 细节: 注意 3,6,9,…,57 和 6,9,…,57,后者甚至不是一个合法的公交路线。我们要避免枚举这种情况,就要保证 x−d<0=>d>x (这样就保证了 x−d 不在 0−59,没有更小的起点)
有问题呀 d > x 第一个合法的等差数列3,6,9,…,57的 d 不大于x 呀
求解答
这个不合法,合法的是0,3,6…,57
0应该不用吧
老哥,不太明白呀。dfs里面为什么要 if(check(x,d)), 加入数组中的不是check过的吗
bus 数组的值会变