2015 USP Try-outs Gym101047

link:http://codeforces.com/gym/101047


Gym 101047D. Random walks in Thailand
题意

给你n个城市,u和v之间距离为 Cuv ,单位距离花费为1,另外还可以选择坐飞机,但是目的地是随机的,也就是说飞机在每个城市降落的可能性一样,坐一次飞机花费为k。求从城市1到n的最小花费的期望值。

思路

fi 表示从i出发到n的最小花费的期望值,则有

fi={0min(min(fj+Cij),u=nu=1fun+k) if i=n if i<n

现在需要求 fi ,任何满足上述公式的 {fi} 都是答案。看上去是高斯消元解方程什么的,其实不然,从公式能看出很多特点来。将 min 函数去掉,就成了差分约束系统,这样就与最短路联系起来了,但还需要解决一个问题,就是 sum=u=nu=1fu 是未知的。
然后重点来了,假定答案已经求出来了为 {fi} sum=fi ,给sum一个增量d,即 sum=sum+d ,根据公式 fi=min(min(fj+Cij),sumn+k) ,由于要满足公式所以 fi 最多增加 dn fi 的增量为 d(n1)n<d=sum 的增量,于是就出来了一个单调关系!!具体来说,二分 sum sumn+k 变成了已知数,然后就可以跑一遍最短路,得到满足公式的 fi ,然后check一下 fi sum 的大小关系来判断假定的sum与正确值相比是偏大还是偏小。
因此步骤变成三步:
1.二分 sum
2.求出满足公式的 fi
3.check fi sum 的关系
关键在于第2步,如何快速求出满足公式的 fi
如果每次将 fi 初始化为 sumn+k ,然后跑一下最短路,最终的 fi 就是满足公式的 fi ,但这样时间复杂度太大。
如果先跑最短路,最后再将每个点与 sumn+k 取个最小值,这样复杂度就很低了, O((n+m)logn+nlogx)

source
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
int head[N], nxt[N + N], to[N + N], weight[N + N], e;
void add(int u, int v, int w) {
  to[e] = v;
  weight[e] = w;
  nxt[e] = head[u];
  head[u] = e++;
}

long double f[N];
int n, m, k;
bool used[N];
bool relax(int u, int v, int w) {
  if (f[u] > f[v] + w) {
    f[u] = f[v] + w;
    return true;
  }
  return false;
}
struct Node {
  int id;
  int val;
  Node(int id = 0, int val = 0) {
    this->id = id;
    this->val = val;
  }
  bool operator< (const Node &that) const {
    return that.val < val;
  }
};
priority_queue<Node> que;
void dijkstra() {
  for (int i = 1; i < n; i++) f[i] = 2e8;
  f[n] = 0;
  for (int i = 1; i <= n; i++) que.push(Node(i, f[i]));
  memset(used, 0, sizeof(used));
  while (!que.empty()) {
    int u = que.top().id;
    que.pop();
    if (used[u]) continue;
    used[u] = true;
    for (int e = head[u]; ~e; e = nxt[e]) {
      int v = to[e], w = weight[e];
      if (relax(v, u, w))  que.push(Node(v, f[v]));
    }
  }
}
long double getSum(long double mid) {
  double sum = 0;
  for (int i = 1; i < n; i++) {
    sum += min(mid / n + k, f[i]);
  }
  return sum;
}
long double solve() {
  dijkstra();
  long double high = 1.0 * k * n * (n - 1), low = 0;
  int cnt = 0;
  while (++cnt < 60) {
    long double mid = (high + low) / 2;
    if (getSum(mid) < mid) high = mid;
    else low = mid;
  }
  return min(f[1], low / n + k);
}
int _;
int main() {
    cin >> _;
    while (_--) {
      cin >> n >> m >> k;
      e = 0;
      memset(head, -1, sizeof(head));
      for (int i = 0; i < m; i++) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        add(u, v, w);
        add(v, u, w);
      }
      cout << setprecision(20) << solve() << endl;
    }
    return 0;
}
Gym 10147F. Fighting the Rajasi
题意

你有一个初始的hp,然后有n个怪兽,每个怪兽有两个属性x和y,表示只有当hp高于x时才能打败它,并且打败它后hp减少x,然后又增加y。另外有k次机会可以直接秒掉怪兽,但是hp不会发生变化。问是否能打败全部的怪兽

思路

将所有怪兽分为两类,第一类是打败它后hp增加的,第二类是打败它后hp减少的。由于打败第一类怪兽后有“福利”,显然应该先打第一类。对于第一类怪兽,把所有能打的打完,如果还剩没有解决的用秒杀机会秒掉。对于第二类怪兽,不能像前面一样能打就打,不同的顺序影响最后的结果。
假设对于一个打的怪兽序列,相邻的两个怪兽是怪兽i(先打)和怪兽j,打怪兽i前hp为 hp ,怪兽i的hp门槛为 xi ,会使hp减少 di ,怪兽j的hp门槛为 xj ,会使hp减少 dj ,则打怪兽j前hp为 hpdi ,那么结果可以表示为:

[hp>=xi][hp>=di+xj]A

交换怪兽i和怪兽j,结果可以表示为:
[hp>=xj][hp>=dj+xi]B

A,B都是布尔表达式,可以变形为:
[hp>=xi][hp>=di+xj][hp>=xj]A

[hp>=xj][hp>=dj+xi][hp>=xi]B

对比A和B发现两项一样,所以只需比较中间那一项,假定交换前更优,那么有: di+xj<dj+xi ,相同下标放一侧得到:
xidi>xjdj

因此,要保证结果最优,怪兽应该按照 xd 也就是 y 从大到小排序!
考虑秒掉部分怪兽,相当于在排序后的怪兽中选一些怪兽出来,把它的x和y都变成0。假设一开始按照排序后的顺序依次打怪兽,并且不考虑怪兽的门槛,令hpi是打完第i个怪兽时的hp(可负)。考虑秒杀怪兽,每秒一个怪兽i,相当于给j从i开始一直到n(假定这里共n个怪兽)的 hpj 增加 di 。那么从i=1开始一直到n,如果碰到了 hpi1<=xi ,则应该从1到i中选一个d值最大的怪兽,秒掉它,然后使 hpk(k>=i) 增加d,这样就用掉了一次秒怪兽的机会。如果碰到这种情况而没有机会了则说明打不完所有怪兽。

source
#include <bits/stdc++.h>
using namespace std;
const int N = 2e3 + 7;
struct Node {
  int x, y;
  void read() {
    scanf("%d%d", &x, &y);
    y -= x;
  }
} p[N];
vector<Node> A, B;
bool used[N];
bool cmp(Node a, Node b) {
  return a.x - a.y > b.x - b.y;
}
int _, n, k;
long long h;
int main() {
    cin >> _;
    while (_--) {
      cin >> n >> h >> k;
      A.clear();
      B.clear();
      for (int i = 0; i < n; i++) {
        p[i].read();
        if (p[i].y >= 0) A.push_back(p[i]);
        else {
          B.push_back(p[i]);
          B[B.size() - 1].y *= -1;
        }
      }
      int nA = A.size(), nB = B.size();
      memset(used, 0, sizeof(used));
      for (int i = 0; i < nA; i++) {
        bool findj = 0;
        for (int j = 0; j < nA; j++) {
          if (!used[j] && h > A[j].x) {
            used[j] = 1;
            h += A[j].y;
            findj = 1;
            break;
          }
        }
        if (!findj) k--;
        if (k < 0) break;
      }
      if (k < 0) {
        puts("N");
        continue;
      }
      sort(B.begin(), B.end(), cmp);
      memset(used, 0, sizeof(used));
      bool ans = 1;
      for (int i = 0; i < nB; i++) {
        if (h > B[i].x) h -= B[i].y;
        else {
          if (k == 0) {
            ans = 0;
            break;
          }
          int findj = i;
          for (int j = 0; j < i; j++) {
            if (!used[j] && B[j].y > B[findj].y) findj = j;
          }
          k--;
          h += B[findj].y - B[i].y;
          used[findj] = 1;
        }
      }
      puts(ans? "Y" : "N");
    } 
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值