小 G 的烦恼
【问题描述】
小 G 最近郁闷死了,MZ 想考考他的智商,给了他一道题,但是小G 退役了这么久,怎么可能做的出来啊?于是他跑去向 quack 大神求助,可是 quack 大神要打牌,于是找到了你,希望能够不让 MZ 失望。
问题是这样的:MZ 想去全球各地旅行。原本是有两家航空公司可以选择的,但是现在那两家公司合并了。然而,在合并初期,两家还没有交接好,于是出现了两家都要收钱的问题。由于 MZ 只想出去玩一个月,她可以选择包月机票(两家公司都有),对于其中的一家公司 A 来说,只要花费 X 元,即可以在持有另一家公司的合法包月机票的情况下,乘坐任何票价不高于 A 公司定价的 X 元的飞机。另一家公司也是一样的。
简单来说,对于航线 i 有 2 种价格 Pi 和 Qi,分别是 A 公司和 B 公司的定价。假设你持有 X 元的 A 公司月票和 Y 元的 B 公司月票,当Pi<=X 并且 Qi<=Y 时,你才可以乘坐航线 i。当然,如果单独购买这一趟航班的两张票也是可以的。
现在 MZ 告诉你了 N-1 个她想要去的城市,MZ 初始时在 1 号位置,并且告诉了你所有航线的两个价格。想要知道,最小的花费。
【输入】
第 1 行 2 个整数 N、M 分别表示城市的个数,航线的条数。
第 2-M+1 行,每行 4 个整数 ui,vi,Pi,Qi,分别表示航线的两个城市(飞机可以来回开),和两种价格。(可能存在重边和自环)
【输出】
共一行,一个整数 ANS,表示最小的花费
【样例输入】
5 5
1 2 3 2
1 3 2 4
2 4 4 2
5 3 3 3
1 4 0 1
【样例输出】
7
【样例解释】
选择除了 2-4 的所有边,所以答案是 3+4=7
【数据范围】
对于 20%的数据 N,M <= 15
对于 50%的数据 N <= 200, M <= 500
对于 80%的数据 N <= 500, M <= 1000
对于 100%的数据 N <= 2000, M <= 5000, Pi, Qi <= 10^9
这题当时的第一反应时二分答案,枚举一边再二分另一半,但是会T,很显然。
然后开始乱搞就想着两次二分,不过确实不太好说明正确性,而且确实不严谨,并且后来跑两趟
(外层二分p内q,外层二分q内p,然后两者取min好像也不是特别好说明为什么就能过,而且这题数据水)
正解是枚举,按照一定的分析和顺序枚举,很巧妙的枚举,也容易懂。
首先将p,q排序,然后外层从大到小枚举p的价格(外层q也是一样的),然后内层从小到大枚举q的价格,在第一次找到能够联通时,break,此时得到一组解
因为p是从大到小枚举的,q是从小到大枚举的,所以如果对于之前的较大的q,那种小的p都不能满足的话,那么后面的小q就更不能满足了,于是内层的p不用每次都从1开始,只需要从之前一次break的地方开始往后找就可以了。
时间复杂度O(2 * M * (N + M))
Code:
枚举:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int Maxn = 2000;
const int Maxm = 5000;
const int INF = 0x3f3f3f3f;
struct FF{
int cost, pos;
bool operator < (const FF & X) const{
return cost < X.cost;
}
bool operator == (const FF & X) const{
return cost == X.cost;
}
}P[Maxm + 5], Q[Maxm + 5];
struct node{
int u, v, p, q;
}Flt[Maxm + 5];
struct E{
int v, nxt;
}edge[(Maxm << 1) + 5];
int N, M, cnt;
int fir[Maxn + 5];
bool vis[Maxn + 5];
bool getint(int & num){
char c; int flg = 1; num = 0;
while((c = getchar()) < '0' || c > '9'){
if(c == '-') flg = -1;
if(c == -1) return 0;
}
while(c >= '0' && c <= '9'){
num = num * 10 + c - 48;
if((c = getchar()) == -1) return 0;
}
num *= flg;
return 1;
}
void addedge(int a, int b){
edge[++ cnt].v = b, edge[cnt].nxt = fir[a], fir[a] = cnt;
edge[++ cnt].v = a, edge[cnt].nxt = fir[b], fir[b] = cnt;
}
void Dfs(int x){
vis[x] = 1;
for(int i = fir[x]; i; i = edge[i].nxt) if(! vis[edge[i].v])
Dfs(edge[i].v);
}
bool Check(int p, int q){
for( ; q <= M && Q[q].cost == Q[q + 1].cost; ++ q);
for( ; p <= M && P[p].cost == P[p + 1].cost; ++ p);
cnt = 0;
memset(fir, 0, sizeof fir );
for(int i = 1; i <= M; ++ i) if(Flt[i].p <= P[p].cost && Flt[i].q <= Q[q].cost)
addedge(Flt[i].u, Flt[i].v);
memset(vis, 0, sizeof vis );
Dfs(1);
for(int i = 1; i <= N; ++ i) if(! vis[i]) return 0;
return 1;
}
int main(){
//freopen("meizi.in", "r", stdin);
//freopen("meizi.out", "w", stdout);
getint(N), getint(M);
for(int i = 1; i <= M; ++ i){
getint(Flt[i].u), getint(Flt[i].v), getint(Flt[i].p), getint(Flt[i].q);
P[i].cost = Flt[i].p, P[i].pos = i;
Q[i].cost = Flt[i].q, Q[i].pos = i;
}
sort(P + 1, P + 1 + M);
//int A = unique(P + 1, P + 1 + M) - P;
sort(Q + 1, Q + 1 + M);
//int B = unique(Q + 1, Q + 1 + M) - Q;
int p = M, q = 1, Ans = INF;
while(p){
for( ; q <= M; ++ q) if(Check(p, q)){
Ans = min(Ans, P[p].cost + Q[q].cost);
break;
}
-- p;
}
printf("%d\n", Ans);
return 0;
}
附上乱搞完全不能保证正确性因数据水而跑得飞快的两趟二分代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int Maxn = 2000;
const int Maxm = 5000;
const int INF = 0x3f3f3f3f;
struct FF{
int cost, pos;
bool operator < (const FF & X) const{
return cost < X.cost;
}
}P[Maxm + 5], Q[Maxm + 5];
struct node{
int u, v, p, q;
bool operator < (const node & X) const{
return p + q < X.p + X.q;
}
}Flt[Maxm + 5];
struct E{
int v, nxt;
}edge[(Maxm << 1) + 5];
int N, M, cnt;
int fir[Maxn + 5];
bool vis[Maxn + 5];
bool getint(int & num){
char c; int flg = 1; num = 0;
while((c = getchar()) < '0' || c > '9'){
if(c == '-') flg = -1;
if(c == -1) return 0;
}
while(c >= '0' && c <= '9'){
num = num * 10 + c - 48;
if((c = getchar()) == -1) return 0;
}
num *= flg;
return 1;
}
void addedge(int a, int b){
edge[++ cnt].v = b, edge[cnt].nxt = fir[a], fir[a] = cnt;
edge[++ cnt].v = a, edge[cnt].nxt = fir[b], fir[b] = cnt;
}
void Dfs(int x){
vis[x] = 1;
for(int i = fir[x]; i; i = edge[i].nxt) if(! vis[edge[i].v])
Dfs(edge[i].v);
}
bool Check(int p, int q){
for( ; q <= M && Q[q].cost == Q[q + 1].cost; ++ q);
cnt = 0;
memset(fir, 0, sizeof fir );
int B = Q[q].cost;
for(int i = 1; i <= p; ++ i){
int tmp = P[i].pos;
if(Flt[tmp].q <= B)
addedge(Flt[tmp].u, Flt[tmp].v);
}
memset(vis, 0, sizeof vis );
Dfs(1);
for(int i = 1; i <= N; ++ i) if(! vis[i]) return 0;
return 1;
}
int Find(int p){
for(; p <= M && P[p].cost == P[p + 1].cost; ++ p);
int dn = max(N - 1 - p, 0), up = M, mid, rt = INF;
while(dn <= up){
mid = (dn + up) >> 1;
if(mid + p < N - 1) dn = mid + 1;
else{
if(Check(p, mid))
rt = Q[mid].cost, up = mid - 1;
else dn = mid + 1;
}
}
if(rt != INF) return rt;
else return 0x7f7f7f7f;
}
int Find1(int q){
for(; q <= M && Q[q].cost == Q[q + 1].cost; ++ q);
int dn = max(N - 1 - q, 0), up = M, mid, rt = INF;
while(dn <= up){
mid = (dn + up) >> 1;
if(mid + q < N - 1) dn = mid + 1;
else{
if(Check(mid, q))
rt = P[mid].cost, up = mid - 1;
else dn = mid + 1;
}
}
if(rt != INF) return rt;
else return 0x7f7f7f7f;
}
int main(){
//freopen("meizi.in", "r", stdin);
//freopen("meizi.out", "w", stdout);
getint(N), getint(M);
for(int i = 1; i <= M; ++ i){
getint(Flt[i].u), getint(Flt[i].v), getint(Flt[i].p), getint(Flt[i].q);
P[i].cost = Flt[i].p, P[i].pos = i;
Q[i].cost = Flt[i].q, Q[i].pos = i;
}
sort(P + 1, P + 1 + M);
sort(Q + 1, Q + 1 + M);
int dn = 1, up = M, mid, all = INF, b;
while(dn <= up){
mid = (dn + up) >> 1;
b = Find(mid);
if(P[mid].cost + b <= all)
all = P[mid].cost + b, up = mid - 1;
else dn = mid + 1;
}
int all2 = INF;
dn = 1, up = M;
while(dn <= up){
mid = (dn + up) >> 1;
b = Find1(mid);
if(Q[mid].cost + b <= all2)
all2 = Q[mid].cost + b, up = mid - 1;
else dn = mid + 1;
}
printf("%d\n", min(all, all2));
return 0;
}