• 赶赴王都[暴力骗分的做法+正解]


    在利贝尔王国王都格兰赛尔正处于一场危机当中,获得消息的小约和小艾正打算赶赴那里,阻止这场阴谋。但是在出发前,他们发生了分歧,小艾希望走最短路,以尽快到达王都,而小约则希望多走不同的道路,以收集情报。后来,他们想到了折衷的办法,选一条路径,使得总路程除以道路数的商最小(即边权平均值最小)。

    输入:给出利贝尔王国的地图。

    第一行为两个整数n,m。表示共n个地点,其中1为小约和小艾的出发点,n为王都。另有m条道路连接这些地点。

         接下来m行,每行3个整数x,y,z,描述一条有向道路。表示x到y有一条长度为z的道路。数据保证,不会出现环,且至少有一条从1到n的路。但两个地点之间可能有多条道路。

         输出:只有一个实数,即最小边权平均值。答案保留两位小数。

         样例输入:4 6

    1 2 1

    2 4 6

    1 3 2

    3 4 4

    2 3 3

    1 4 8

                  

         样例输出:2.67

         数据范围:30% 的数据1<=n<=20,1<=m<=20

                   100%的数据1<=n<=10^3,,1<=m<=20000 ,其余数据在[0,10^3]。

    据40大神说是01分数规划= = 人弱不会> <

    但是,学习了一下这题的暴力做法,觉得还是有启发的;

    dist[i][j]表示的从1~i,走过j条边的最小值;

    vis[i][j]类推;

    还是一遍spfa,但是因为dist是二维的,还记录了走过了多少条边,需要转移边的个数,所以写起来也有小小的区别;

    首先queue,因为你入队的有第几个结点,和走过多少条边,所以queue 定义为node

    之后同理,松弛操作的时候 多一个转移边的个数即可;

    具体的看程序:

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <vector>
    #include <queue>
    using namespace std;
    const int maxn=1001,maxm=20001;
    int dist[maxn][maxm],vis[maxn][maxm];
    int n,m;
    struct edge{
    	int to,w;
    	edge(int _to,int _w){to=_to;w=_w;}
    };
    vector <edge> g[maxm];
    int x,y,z;
    double minx=1234567844.0;
    struct node{
    	int x,num;
    	node(int _x,int _num){x=_x;num=_num;}
    };
    queue <node> q;
    
    void spfa(int x){
    	memset(dist,63,sizeof(dist));
    	dist[1][0]=0;
    	vis[1][0]=1;
    	q.push(node(1,0));
    	
    	while(!q.empty()){
    		node temp=q.front();//注意这里; 
    		q.pop();
    		int u=temp.x;
    		vis[u][temp.num]=0;
    		int l=g[u].size();
    		for(int i=0;i<l;i++){
    			int v=g[u][i].to;
    			if(dist[v][temp.num+1]>dist[u][temp.num]+g[u][i].w){
    				dist[v][temp.num+1]=dist[u][temp.num]+g[u][i].w;
    				if(!vis[v][temp.num+1]){
    					q.push(node(v,temp.num+1));
    				}
    			}
    		} 
    	}
    }
    
    int main(){
    	freopen("troops.in","r",stdin);
    	freopen("troops.out","w",stdout);
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=m;i++){
    		scanf("%d%d%d",&x,&y,&z);
    		g[x].push_back(edge(y,z));
    	}
    	spfa(1);
    	for(int i=1;i<=m;i++){
    		double ans=dist[n][i]/(i+0.0);
    	    if(ans<minx) minx=ans;
    	}
    	printf("%.2f",minx);
    	return 0;
    }
    

    这题的正解是01分数规划。之前某位大神有跟我讲过这个问题,但是由于他讲得太深奥(一定是我太傻逼),一直觉得01分数规划是一个奇怪的东西.....还好看题解看明白了恩

    正解:

    事实上,要使平均值最小是很困难的,相反,如果知道平均值,求是否存在一条路径的平均值不大于这个值却不困难。所有边权都减去这个平均值,再套用可以处理负权的算法(如spfa),最后判断最短路径长度是否小于等于0即可。同时题目保证无环,不需要处理负环的情况。

    现在的问题就只剩如果确定这个平均值。不难发现,我们上面的定义是满足二份性质的(因为如果平均值增大,则所有新图的边权都减少,最短路径长度必然减小,反之亦然)。

    那么总的算法就是先二分答案,然后spfa判断。总时间复杂度是O(km log ans)。其中ans是答案最大值,kspfa的常数。

    (和前几天写的搭电线的那题挺像的= = 其实就是二分+spfa验证答案....)

    哦顺便提一下,我是打暴力打过的,据40说正解的二分答案如果是while(r-l<0.001)会出现精度问题...所以要小心(但是暴力就没有这样的问题了)

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<iostream>
    #include<queue>
    #include<cmath>
    #include<map>
    #include<set>
    #include<cstdlib>
    using namespace std;
    const int INF = 2147483647,maxn = 10005,maxm = 200005;
    
    struct edge{
      int to,nxt,v;
    }G[maxm*2];
    
    int n,m,tot,first[maxn],b[maxn];
    double dis[maxn];
    
    void add(int x,int y,int v){
      G[++tot].nxt = first[x]; first[x] = tot; G[tot].to = y; G[tot].v = v;
    }
    
    queue<int>d;
    
    bool spfa(double x){
      for(int i=1;i<=n;i++)dis[i] = 345676542;
      memset(b,0,sizeof(b));
      d.push(1);
      dis[1] = 0;
      b[1] = 1;
      while(!d.empty()){
        int tmp = d.front();d.pop();b[tmp] = 0;
        for(int i=first[tmp];i;i=G[i].nxt)
          if(dis[G[i].to]>dis[tmp]+G[i].v-x){
            dis[G[i].to] = dis[tmp] + G[i].v - x;
            if(!b[G[i].to])d.push(G[i].to),b[G[i].to] = 1;
          }
      }
      if(dis[n]<0)return 1;else return 0;
    }
    
    int main(){
      //freopen("data","r",stdin);
      freopen("run.in","r",stdin);
      freopen("run.out","w",stdout);
      scanf("%d%d",&n,&m);
      int x,y,v;double l = 0,r = 0;
      for(int i=1;i<=m;i++)scanf("%d%d%d",&x,&y,&v),add(x,y,v),r = max(r,(double)v);
      for(int k=1;k<=100;k++){
        double mid = (l+r)/2;
        if(spfa(mid))r = mid;else l = mid; 
      }
      printf("%.2lf",l);
      return 0;
    }
    

      

  • 相关阅读:
    [POJ 1200]Crazy Search
    [来源不详]删数方案数
    noip搜索模拟题 骰子
    [SDOI2010]地精部落
    [HAOI2008]硬币购物
    BZOJ1009: [HNOI2008]GT考试
    BZOJ1830: [AHOI2008]Y型项链 & BZOJ1789: [Ahoi2008]Necklace Y型项链
    BZOJ1251: 序列终结者
    BZOJ3620: 似乎在梦中见过的样子
    BZOJ4477: [Jsoi2015]字符串树
  • 原文地址:https://www.cnblogs.com/polebug/p/4070281.html
Copyright © 2020-2023  润新知