题意:
有n座城市,m条双向的航线连接它们,每一条航线有两个值,分别是p,q,假设你付出x,y的花费那么对于所有满足p<=x&&q<=y的航线,都可以免费乘坐(其实题目本身来说是有一个小弯的,就是说如果你付出的x,y并不能让你乘坐那条航线,你也可以额外付这一条航线钱,但显然,这是不优的),求最小花费(有自环和重边)
题解:
枚举第x,二分y,然后判断图是否联通,理论上是80分的做法,但其实是可以AC的(数据水),这里不做讨论,而是讨论出题人的满分做法,这个做法真的十分巧妙
先转换下问题:我们要做的就是在这m条边中选出n-1条,使图联通,并且这些边中的p的最大值和q的最大值之和最小
那么现在就比较清晰了,可以发现,如果我们不下降地枚举p值(这里也就是x),那么如果找到了一个能使图联通的q(这里也就是y),那么p值增大后这个q也自然是可以使图联通的,所以如果有新的q使图联通,最优值的q一定小于当前q,即:在以单调不下降的方式枚举q后,p值是单调不上升的
膜拜gen4512大牛(不过话说为啥要取这个名字,貌似数字对应的汉字意思有点不对劲啊)
先附上考试时写的80分代码(里面有我考试时想的思路),最小生成树判联通,m>5000时用明知道是错的的两次二分,唉,不想说话
80分code:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
const int MAXN=2005;
const int MAXM=5005;
typedef long long LL;
using namespace std;
int n,m,cnt=1,fa[MAXN],height[MAXN];
int l,r,s,e,mid,ans,w,temp=0x3f3f3f3f;
int q[MAXM];
struct node{int s,e,a,b;}edge[MAXM<<1];
inline void Read(int &Ret){
char ch;bool flag=0;
for(;ch=getchar(),ch<'0'||ch>'9';)if(ch=='-')flag=1;
for(Ret=ch-'0';ch=getchar(),'0'<=ch&&ch<='9';Ret=Ret*10+ch-'0');
flag&&(Ret=-Ret);
}
inline void addedge(int t1,int t2,int a,int b){
edge[++cnt].s=t1;
edge[cnt].e=t2;
edge[cnt].a=a; edge[cnt].b=b;
edge[++cnt].s=t2;
edge[cnt].e=t1;
edge[cnt].a=a; edge[cnt].b=b;
}
inline int root(int t){
while(fa[t]) t=fa[t];
return t;
}
inline void connect(int t1,int t2){
if(height[t1]>height[t2]) fa[t2]=t1;
else if(height[t2]>height[t1]) fa[t1]=t2;
else height[t2]++,fa[t1]=t2;
}
bool judge(int lima,int limb){
for(int i=1;i<=2*n;i++)
height[i]=0,fa[i]=0;
int ecnt=0;
for(int i=2;i<=cnt;i++)
{
int t1=root(edge[i].s);
int t2=root(edge[i].e);
if((t1!=t2||!t1)&&edge[i].a<=lima&&edge[i].b<=limb)
{
connect(t1,t2); ecnt++;
if(ecnt==n-1) return 1;
}
}
return 0;
}
bool check(int lima){
ans=0x3f3f3f3f; s=0; e=m;
if(!judge(lima,q[e])) return 0;
while(s<e)
{
ans=(s+e)>>1;
if(judge(lima,q[ans])) e=ans;
else s=ans+1;
}
ans=q[(s+e)>>1];
return 1;
}
int main()
{
//freopen("meizi.in","r",stdin);
//freopen("meizi.out","w",stdout);
Read(n); Read(m);
for(int i=1;i<=m;i++)
{
int u,v,p;
Read(u); Read(v);
Read(p); Read(q[i]);
if(u==v) continue;
r=max(r,p);
addedge(u,v,p,q[i]);
}
sort(q+1,q+m+1);
if(n<=1){printf("%d\n",0);return 0;}
if(m<5000)
{
for(int i=2;i<=cnt;i+=2)if(check(edge[i].a))
temp=min(temp,ans+edge[i].a);
}
else
{
while(l<r)
{
mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
temp=min(temp,ans+mid);
}
}
printf("%d\n",temp);
}
/*
???最小生成树
问题转换
首先对于这个问题来说,想要环游所有城市
飞机所形成的线路一定是一颗树,而问题可以
转化成,要使这颗树的两个边权最大值之和
最小
树形dp?
定义状态dp[i]表示以i为根的子树的边权最大值之和
的最小值maxa[i],maxb[i]储存以i为根的子树的边权
最大值
不对啊,这个只是答案是个树啊
图论?
cost[i][j]表示i到j的最小费用
不对,我还是觉得最小生成树最靠谱
要不要套两个二分?
第一次二分p[i],二分p[i]之后,check里面二分q[i]
q[i]的check里面用最小生成树的思想判断在不大于
p[i],q[i]的限制下,能否形成一颗树,若能,返回1
不能返回0
算一下时间复杂度
两次二分是log(p[i])*log(q[i])*mlogn
貌似能过诶
那会不会出现这种情况,就是第1个可以二分出较低
的答案,但是第二个就偏高,那么第一个就得枚举
所以说,现在的时间复杂度似乎应该是炸掉了
boom! 阿西吧,开启骗分模式
哎,不过要是我把它优化成m*log(m)*m*log(n)勒
还是要炸啊,气死我了
5 5
1 2 3 2
1 3 2 4
2 4 4 2
5 3 3 3
1 4 0 1
*/
正解:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
const int MAXN=2005;
const int MAXM=5005;
using namespace std;
int fir[MAXN],cnt=1;
int p[MAXM],q[MAXM];
int lima,limb,u,v,n,m;
struct node{
int e,next,p,q;
}h[MAXM<<1];
queue<int> Q; bool vis[MAXN];
int ans=0x7f7f7f7f;
inline void Read(int &Ret){
char ch;bool flag=0;
for(;ch=getchar(),ch<'0'||ch>'9';)if(ch=='-')flag=1;
for(Ret=ch-'0';ch=getchar(),'0'<=ch&&ch<='9';Ret=Ret*10+ch-'0');
flag&&(Ret=-Ret);
}
inline void addedge(int t1,int t2,int p,int q){
h[++cnt].e=t2;
h[cnt].next=fir[t1];
fir[t1]=cnt;
h[cnt].p=p; h[cnt].q=q;
}
inline bool bfs(){
memset(vis,0,sizeof vis);
Q.push(1); vis[1]=1; int ncnt=0;
while(!Q.empty())
{
int s=Q.front(); Q.pop(); ncnt++;
for(int i=fir[s];i;i=h[i].next)
if(!vis[h[i].e]&&h[i].p<=lima&&h[i].q<=limb)
Q.push(h[i].e),vis[h[i].e]=1;
}
if(ncnt<n) return 0;
ans=min(ans,lima+limb);
return 1;
}
int main()
{
Read(n); Read(m);
for(int i=1;i<=m;i++)
{
Read(u); Read(v); Read(p[i]); Read(q[i]);
addedge(u,v,p[i],q[i]);
addedge(v,u,p[i],q[i]);
}
sort(p+1,p+m+1); sort(q+1,q+m+1);
int j=m; limb=q[j];
for(int i=n-1;i<=m;i++)
{
lima=p[i];
while(j>=n-1&&bfs())
limb=q[--j];
}
printf("%d",ans); putchar(10);
}