noip膜你赛day2第二题 小G的烦恼

题意:

有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);
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值