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∗(n−1)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为
hp−di
,那么结果可以表示为:
[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 ,相同下标放一侧得到:
xi−di>xj−dj
因此,要保证结果最优,怪兽应该按照 x−d 也就是 y 从大到小排序!
考虑秒掉部分怪兽,相当于在排序后的怪兽中选一些怪兽出来,把它的x和y都变成0。假设一开始按照排序后的顺序依次打怪兽,并且不考虑怪兽的门槛,令
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;
}