我喜欢这题面。题意大概是一群人喜欢女装,但是不好意思直接女装,而是希望通过一些巧妙的借口借机成功女装,他们的借口是这样的:① A 与 B 的分数相比,若 ,则 A 女装;② A 与 B 的分数相比,若 ,则 A 女装(分数什么的,还不好操控吗?)。现在,需要做的是,求出最大的 T,使得在 ①的 k 变为 k-T,②的 k 变为 k+T 的情况下,仍然有人女装。
其实,这就相当于求满足与的 k-T、1/(k+T)的最小值,也就是 T 的最大值。
那么问题来了,怎么求?倘若多观察一下这些式子,可以发现,这些式子与差分约束的公式(如)竟然有几分神似。可是差分约束的式子中,两个变量执行的是相减操作,而这道题执行的是相除操作,有没有一种方法可以把除法变为减法呢?当然有,那就是取对数。假使我们对以上两个式子的两边同时取对数,就会得到与,这样就变成与差分约束一样啦,尽管第二个式子是大于而不是大于等于,因为题目给的是 SPJ,容许有一点误差,所以这个大于而非大于等于产生的误差是可以接受的。取完对数以后,就可以使用差分约束的方法来建图了。
另外,我们还观察到,k 的取值范围为 [1, 10],而为了确保 k-T>0,T 的取值范围也就限制在这里面了。由于 T 的取值范围非常小,所以可以采用二分枚举 T 的方法,对每个 mid 跑一遍 spfa,找到满足条件的最终解就行了。
最后,很值得注意的一点,关于链式前向星的。在差分约束中使用链式前向星,edge 数组尽量开大一些。因为差分约束经常会增设虚拟点、虚拟边,使得边数大大增加,一不小心就会 RE。本题就是开了三倍的空间才过的。
#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 3e3+3;
struct Edge
{
int to, kind, next;
double w, k;
Edge() {}
Edge(int to, double w, int kind, double k, int next): to(to), w(w), kind(kind), k(k), next(next) {}
};
int n, s, t;
Edge edge[maxn];
int head[maxn], cnt;
int vis[maxn], c[maxn];
double dis[maxn];
double l = 0, r = 10;
void init()
{
for(int i = 0; i < maxn; ++i)
head[i] = -1;
cnt = 0;
}
void addEdge(int u, int v, double w, int kind, double k)
{
edge[cnt] = Edge(v, w, kind, k, head[u]);
head[u] = cnt++;
}
void read()
{
cin >> n >> s >> t;
int o, a, b, c;
double k, x;
for(int i = 0; i < s; ++i)
{
cin >> o >> a >> b >> k;
addEdge(b, a, 0, o, k);
if(o == 1) //当o为1时,k-T要为正
r = min(r, k);
}
for(int i = 0; i < t; ++i)
{
cin >> c >> x;
addEdge(0, c, log2(x), 0, 0); //差分约束常用策略:已知具体数值,
addEdge(c, 0, -log2(x), 0, 0); //则连边到一个虚拟点中,取大于等于和小于等于夹逼该具体值
}
}
bool spfa(double T)
{
queue<int> q;
for(int i = 1; i <= n; ++i) //初始化
{
vis[i] = 0;
c[i] = 0;
dis[i] = -INF;
}
q.push(0); //虚拟点入队
vis[0] = 1;
dis[0] = 0;
++c[0];
while(!q.empty())
{
int u = q.front();
q.pop();
vis[u] = 0;
for(int i = head[u]; ~i; i = edge[i].next)
{
int v = edge[i].to, kind = edge[i].kind;
double w;
if(!kind) //已知c的具体数值
w = edge[i].w;
else if(kind == 1) //o为1
w = log2(edge[i].k-T);
else if(kind == 2) //o为2
w = -log2(edge[i].k+T);
if(dis[v] < dis[u]+w) //求最长路
{
dis[v] = dis[u]+w;
if(!vis[v])
{
q.push(v);
vis[v] = 1;
if(++c[v] >= n) //存在负环
return 0;
}
}
}
}
return 1;
}
void solve()
{
for(int i = 1; i <= n; ++i) //差分约束常用策略:图可能不连通
addEdge(0, i, 0, 0, 0); //则设置一个虚拟点,并向每个其他点连一条指向该点的边
bool flag = 0;
while(r-l > 1e-5) //二分枚举T的值
{
double mid = (r+l)/2.0;
if(spfa(mid))
r = mid;
else
l = mid, flag = 1;
}
if(flag)
cout << l << endl;
else
cout << -1 << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
init();
read();
solve();
return 0;
}