[洛谷P4926] 倍杀测量者(差分约束)

传送门:P4926 [1007]倍杀测量者

我喜欢这题面。题意大概是一群人喜欢女装,但是不好意思直接女装,而是希望通过一些巧妙的借口借机成功女装,他们的借口是这样的:① A 与 B 的分数相比,若 \frac{a}{b}< k,则 A 女装;② A 与 B 的分数相比,若 \frac{b}{a} > k,则 A 女装(分数什么的,还不好操控吗?)。现在,需要做的是,求出最大的 T,使得在 ①的 k 变为 k-T,②的 k 变为 k+T 的情况下,仍然有人女装。

其实,这就相当于求满足\frac{a_{i}}{b_{i}} \geq k_{i}-T\frac{a_{i}}{b_{i}}> \frac{1}{k_{i}+T}的 k-T、1/(k+T)的最小值,也就是 T 的最大值。

那么问题来了,怎么求?倘若多观察一下这些式子,可以发现,这些式子与差分约束的公式(如x_{i}- x_{j} \geq c_{i})竟然有几分神似。可是差分约束的式子中,两个变量执行的是相减操作,而这道题执行的是相除操作,有没有一种方法可以把除法变为减法呢?当然有,那就是取对数。假使我们对以上两个式子的两边同时取对数,就会得到\log\!a-\log\!b\geq \log\! (k-T)\log\!a-\log\!b> -\log\! (k+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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值