UVa1516/LA5906 Smoking gun

题目链接

  本题是2011年icpc欧洲区域赛西北欧赛区G

  UVA - 1516 Smoking gun

题意

  蛮荒的西部经常发生枪击事件,某场枪击事件现场有n个人(2≤n≤100),给出每个人的坐标(0≤x,y≤1000000),以及他们听到的枪击顺序(‘S1 heard S2 firing before S3’),请帮助法官判定所有人的陈述是否成立以及能否唯一确定枪击者的顺序(没出现在陈述为S2、S3的人认为没开枪,并且越早开枪的人量刑越重),陈述有矛盾则输出IMPOSSIBLE,能唯一确定枪击者的顺序则按先后顺序输出其名字,不能唯一确定顺序则输出UNKNOWN。

分析

  题目原文已经对sample做了解释,说要考虑声音的传播速度340m/s,由此可以想到构建差分约束求解:设第 i i i个人开枪的时刻为 t i t_i ti,对于陈述‘ S k S_k Sk heard S i S_i Si firing before S j S_j Sj’可写出不等式 t i + ( x i − x k ) 2 + ( y i − y k ) 2 340 < t j + ( x j − x k ) 2 + ( y j − y k ) 2 340 t_i+\frac{\sqrt{(x_i-x_k)^2+(y_i-y_k)^2}}{340}<t_j+\frac{\sqrt{(x_j-x_k)^2+(y_j-y_k)^2}}{340} ti+340(xixk)2+(yiyk)2 <tj+340(xjxk)2+(yjyk)2 t i − t j < ( x j − x k ) 2 + ( y j − y k ) 2 − ( x i − x k ) 2 + ( y i − y k ) 2 340 t_i-t_j<\frac{\sqrt{(x_j-x_k)^2+(y_j-y_k)^2}-\sqrt{(x_i-x_k)^2+(y_i-y_k)^2}}{340} titj<340(xjxk)2+(yjyk)2 (xixk)2+(yiyk)2 ,还可以将声速拿掉,将差分约束简化为 t i ′ − t j ′ < ( x j − x k ) 2 + ( y j − y k ) 2 − ( x i − x k ) 2 + ( y i − y k ) 2 t_i^{'}-t_j^{'}<\sqrt{(x_j-x_k)^2+(y_j-y_k)^2}-\sqrt{(x_i-x_k)^2+(y_i-y_k)^2} titj<(xjxk)2+(yjyk)2 (xixk)2+(yiyk)2

  这和一般的差分约束不太一样,一般的差分约束不等式带等号,无解等价于有向图存在负权圈(跑一下Bellman Ford即可),这里差分约束不带等号,那么0权圈也是无解的。并且本题有解时还需要判断拓扑排序结果是否唯一,考虑顶点数n并不大(2≤n≤100),可以用Floyd算法预处理之后,有0权圈或负权圈(即w[i][i]≤0)则无解,有解时利用dp找最长路(权值为正的有向边不考虑,因为对应的不等式为 t i ′ − t j ′ < 正数 t_i^{'}-t_j^{'}<正数 titj<正数,代表i可能在j之后开枪也可能在j之前开枪)并根据其长度是否等于开枪总人数确定拓扑排序结果是否唯一。

测试数据

  给一份测试数据

AC 代码

#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;

#define N 102
int f[N], s[N], m, n; double w[N][N]; char names[N][22], t[22]; long long x[N], y[N];

int idx() {
    cin >> t;
    for (int i=0; i<n; ++i) if (!strcmp(names[i], t)) return i;
    return n;
}

int dp(int u) {
    if (f[u] > 0) return f[u];
    f[u] = 1; s[u] = -1;
    for (int v=0; v<n; ++v) if (w[u][v] <= 0. && 1 + dp(v) > f[u]) f[u] = 1 + f[v], s[u] = v;
    return f[u];
}

void solve() {
    cin >> n >> m;
    for (int i=0; i<n; ++i) cin >> names[i] >> x[i] >> y[i], f[i] = 0;
    for (int i=0; i<n; ++i) for (int j=0; j<n; ++j) w[i][j] = 1e9;
    while (m--) {
        int u = idx(), v, z; cin >> t; v = idx(); cin >> t >> t; z = idx(); f[v] = f[z] = 1;
        w[v][z] = min(w[v][z], sqrt((x[z]-x[u])*(x[z]-x[u]) + (y[z]-y[u])*(y[z]-y[u])) - 
                                sqrt((x[v]-x[u])*(x[v]-x[u]) + (y[v]-y[u])*(y[v]-y[u])));
    }
    for (int k=0; k<n; ++k) for (int i=0; i<n; ++i) for (int j=0; j<n; ++j) {
        w[i][j] = min(w[i][j], w[i][k] + w[k][j]);
        if (w[i][j] + w[j][i] <= 0.) {
            cout << "IMPOSSIBLE" << endl;
            return;
        }
    }
    for (int i=m=0; i<n; ++i) if (f[i]) ++m, f[i] = 0;
    for (int i=0; i<n; ++i) if (dp(i) == m) {
        cout << names[i];
        while (s[i] >= 0) cout << ' ' << names[i = s[i]];
        cout << endl;
        return;
    }
    cout << "UNKNOWN" << endl;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int t; cin >> t;
    while (t--) solve();
    return 0;
}
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值