[APIO2017]商旅 0/1分数规划

~~~题面~~~

题解:

upd: 在洛谷上被Hack了。。。思路应该是对的,代码就别看了

感觉有个地方还是非常妙的,就是因为在x买东西,在y卖出,就相当于直接从x走向了y,因为经过中间的城市反正也不会造成任何影响。

所以建图就可以直接把所有城市两两连边,然后直接枚举找出使得在x买东西,在y卖出的利润最大化的物品,并将利润作为权值,(a[i])

再floyd找出x到y的最短路径(这大概就算用到城市可以重复经过的条件了吧)作为代价,(b[i])

接下来就和[HNOI2009]最小圈非常类似了。

只不过这里是要找最大利润,而我们是用spfa找负环,所以可以对边权取相反数,(当然你也可以不变边权找正环)

此外还有一个需要注意的地方,

如果你是二分小数自然没有任何问题,

但是如果你是二分整数就要注意判断0环了(因为答案是向下取整的),因为如果你二分小数的话,反正你会多二分几次来保证精度,所以不判断是没有什么问题的,

但是整数不一样,所以要判断0环,方法也很简单,在dfs版spfa里面的判断条件上加上=就可以了

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define R register int
  4 #define AC 150
  5 #define ac 21000
  6 #define getchar() *o++
  7 #define inf 2139062143
  8 char READ[5001000],*o=READ;
  9 int n,m,l,mid,maxn;
 10 int buy[AC][AC*10],sale[AC][AC*10],s[AC][AC],f[AC][AC],dis[ac];
 11 int date[ac],Next[ac],Head[AC],length[ac],tot;
 12 bool vis[AC],flag;
 13 /*想了很久如何处理异地交易的问题,因为中间走过的路程会被忽略,
 14 所以实际上就相当于直接从买入地x走到了卖出地y,
 15 所以直接两两连边,因为反正可以重复经过,所以走一条边就相当于略过了中间的所有边,
 16 然后为了尽量节省时间,所以先处理出任意两点之间的最短路,因为不管怎么走收益不变,
 17 但代价变了,所以连边要连最短路,所以先floyd处理一下,再把结果连成边,
 18 不过利益不变的前提是买同一种商品,但是商品不止一种,所以还要处理出最大收益,
 19 因为地点都已经固定了,所以这个时候就只要枚举一下买哪种划得来了,
 20 标准就是买卖单个的收入就行了
 21 然后动态改一下权值就好了,dfs版spfa找负环*/
 22 inline int read()
 23 {
 24     int x=0;char c=getchar();bool z=false;
 25     while(c > '9' || c < '0') {if(c == '-') z=true; c=getchar();}
 26     while(c >= '0' && c <= '9') x=x*10+c-'0',c=getchar();
 27     if(!z) return x;
 28     else return -x;
 29 }
 30 
 31 inline void add(int f,int w)
 32 {
 33     date[++tot]=w,Next[tot]=Head[f],Head[f]=tot;
 34 }
 35 
 36 inline void upmin(int &a,int b)
 37 {
 38     if(b < a) a = b; 
 39 }
 40 
 41 inline void upmax(int &a,int b)
 42 {
 43     if(b > a) a = b;
 44 }
 45 
 46 void pre()
 47 {
 48     int a,b,c;
 49     n=read(),m=read(),l=read();
 50     memset(f,127,sizeof(f));
 51     for(R i=1;i<=n;i++)
 52     {
 53         f[i][i]=0;
 54         for(R j=1;j<=l;j++)
 55             buy[i][j]=read() , sale[i][j]=read();
 56     }
 57     for(R i=1;i<=m;i++)
 58     {
 59         a=read(),b=read(),c=read();
 60         upmin(f[a][b],c);//这里就要upmin。,,,,不然可能会被不优的边覆盖
 61     }
 62 }
 63 
 64 void init()
 65 {
 66     /*for(int i=1;i<=n;i++)
 67     {
 68         for(int j=1;j<=n;j++)
 69             printf("%d ",f[i][j]);
 70         printf("
");
 71     }
 72     printf("!!!
");*/
 73     for(R k=1;k<=n;k++)    
 74         for(R i=1;i<=n;i++)
 75         {
 76             if(f[i][k] == inf) continue;
 77             for(R j=1;j<=n;j++)
 78                 if(f[k][j] != inf) upmin(f[i][j] , f[i][k] + f[k][j]);
 79         }//怕爆了
 80     for(R i=1;i<=n;i++)//枚举起点
 81         for(R j=1;j<=n;j++)//枚举终点
 82         {
 83             if(i == j) continue;
 84             if(f[i][j] == inf) continue; 
 85             for(R k=1;k<=l;k++) //枚举买哪种
 86             {
 87                 if(sale[j][k] == -1 || buy[i][k] == -1) continue;
 88                 upmax(s[i][j],sale[j][k] - buy[i][k]);//获取利润                                                                                                                                                                                                                                                    
 89                 upmax(maxn,s[i][j] / f[i][j]);
 90             }
 91         }
 92     /*for(int i=1;i<=n;i++)
 93     {
 94         for(int j=1;j<=n;j++)
 95             printf("%d ",f[i][j]);
 96         printf("
");
 97     }
 98    // printf("---------------
");
 99     printf("
");
100     for(int i=1;i<=n;i++)
101     {
102         for(int j=1;j<=n;j++)
103             printf("%d ",s[i][j]);
104         printf("
");
105     }*/
106     for(R i=1;i<=n;i++)
107         for(R j=1;j<=n;j++)
108         {
109             if(i == j) continue;
110             if(f[i][j] == inf) continue;//error,,,这里不能加上!s[i][j],虽然没有利润,但仍然可以起到连接作用
111             add(i,j);
112         }
113 }
114 
115 void spfa(int x)
116 {
117     int now;
118     vis[x]=true;
119     for(R i=Head[x]; i ;i=Next[i])
120     {
121         now=date[i];
122         if(dis[now] >= dis[x] + length[i])//找负环肯定是最短路啊
123         {//加个等号判0环?
124             if(vis[now]) {flag=true;break;}
125             dis[now] = dis[x] + length[i];
126             spfa(now);
127         }
128     }
129     vis[x]=false;
130 }
131 
132 bool check()
133 {
134     int now;
135     flag=false;
136     memset(dis,0,sizeof(dis));
137     for(R i=1;i<=n;i++)
138         for(R j=Head[i]; j ;j=Next[j])
139         {
140             now=date[j];
141             length[j]=mid * f[i][now] - s[i][now];//因为要用找负环的方法找正环,所以边权取相反
142         }//error,,是f[i][now]
143     for(R i=1;i<=n;i++)
144     {
145         spfa(i);
146         if(flag) return true;
147     }
148     return false;
149 }
150 
151 void half()
152 {
153     int l=0,r=maxn;
154     while(r - l > 1)
155     {
156         mid=(l + r) / 2;
157         if(check()) l=mid;
158         else r=mid;
159     }
160     if(r > l)
161     {
162         mid=r;
163         if(check()) printf("%d
",r);
164         else printf("%d
",l);
165     }
166     else printf("%d
",l);
167 }
168 
169 int main()
170 {
171 //    freopen("in.in","r",stdin);
172     fread(READ,1,5000000,stdin);
173     pre();
174     init();
175     half();
176 //    fclose(stdin);
177     return 0;
178 }
原文地址:https://www.cnblogs.com/ww3113306/p/9143677.html