P5122 [USACO18DEC]Fine Dining G
题意:
有 n 个点 m 条无向边,终点为点 n 。有 k 个特殊点,给出每个点的位置,以及一个值 val 。当你到达一个点,你的路径长度会减少 val 。路径长度最多只能减一次。
问每一个从 i 出发到 n 的路径是否可以在不增加路径长度的前提下经过有 val 的点。
题解:
先做一遍dijkstra,算出所有点到 n 的最短路的距离,再遍历所有的位置,如果当前点有 val ,则将当前点的 val 更新为 dist - val ,然后再做一遍对 val 的dijkstra,看看以这些点为起点时,其他点能不能达到或者小于之前的 dist 。
具体代码如下:
#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#define int long long
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<LL, LL> PLL;
typedef pair<int, int> PII;
const int N = 100010, M = 100010;
const int MOD = 998244353;
const int INF = 0x7fffffff;
int n, m, T, k;
int res;
int h[N], e[N * 2], ne[N * 2], w[N * 2], idx;
int val[N];
int dist[N];
bool st[N];
priority_queue<PII, vector<PII>, greater<PII> > heap;
int dx[] = {1, -1, 0, 0}, dy[] = {0, 0, 1, -1};
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dijkstra1()
{
memset(dist, 0x3f, sizeof dist);
dist[n] = 0;
heap.push({0, n});
while (heap.size())
{
PII t = heap.top();
heap.pop();
int ver = t.second, dis = t.first;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
heap.push({dist[j], j});
}
}
}
}
void dijkstra2()
{
while (heap.size()) heap.pop();
memset(st, false, sizeof st);
for (int i = 1; i < n; i ++ )
{
if (!val[i]) val[i] = 0x3f3f3f3f;
else
{
val[i] = dist[i] - val[i];
heap.push({dist[i], i});
}
}
while (heap.size())
{
PII t = heap.top();
heap.pop();
int ver = t.second, dis = t.first;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (val[j] > val[ver] + w[i])
{
val[j] = val[ver] + w[i];
heap.push({val[j], j});
}
}
}
}
void solve()
{
memset(h, -1, sizeof h);
cin >> n >> m >> k;
for (int i = 1; i <= m; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
for (int i = 1; i <= k; i ++ )
{
int p, v;
cin >> p >> v;
val[p] = max(val[p], v);
}
dijkstra1();
dijkstra2();
for (int i = 1; i < n; i ++ )
{
if (val[i] <= dist[i]) cout << "1" << endl;
else cout << "0" << endl;
}
}
signed main()
{
quick_cin();
// cin >> T;
T = 1;
while (T -- )
{
solve();
}
return 0;
}
P5201 [USACO19JAN]Shortcut G
题意:
给定一个有 n 个结点和 m 条边的带权无向图,每个结点 i 上有 ci 头奶牛, 1 号结点为终点,每次去终点奶牛会走一条最短的路径,如果有多条长度相同的最短路径,则奶牛会走字典序最小的一条。现在,你可以增加一条从 1 到任意结点的长度为给定值 T 的一条边。如果一头奶牛在平时回家的路上经过了这条边相连的结点,且这条边能使其回家路径更短,则其会走这条边。求能使所有奶牛走的路径长度和的变化的最大值。
题解:
从结点 1 开始做最短路,建出最短路树(dijkstra), DFS 遍历最短路树,对最短路树上的一点,其子树中的点在回家的过程中都会经过该点。此时若连接一条从该节点到 1 号结点的边,则其最短路长度总和会减少 (dist[i] - t) * sz[i] 其中sz[i]表示每个节点的子树的奶牛总和,遍历所有减少的最大值。
具体代码如下:
#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#define int long long
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<LL, LL> PLL;
typedef pair<int, int> PII;
const int N = 100010, M = 100010;
const int MOD = 998244353;
const int INF = 0x7fffffff;
int n, m, T, k;
int res;
int h[N], e[N * 2], ne[N * 2], w[N * 2], idx;
int num[N];
int dist[N];
bool st[N];
int sz[N];
vector<int> g[N];
priority_queue<PII, vector<PII>, greater<PII> > heap;
int dx[] = {1, -1, 0, 0}, dy[] = {0, 0, 1, -1};
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
heap.push({0, 1});
while (heap.size())
{
PII t = heap.top();
heap.pop();
int ver = t.second, dis = t.first;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
heap.push({dist[j], j});
}
}
}
}
int dfs(int u, int fa)
{
sz[u] += num[u];
for (int i = 0; i < g[u].size(); i ++)
{
int j = g[u][i];
if (j == fa) continue;
sz[u] += dfs(j, u);
}
res = max(res, (dist[u] - k) * sz[u]);
return sz[u];
}
void solve()
{
memset(h, -1, sizeof h);
cin >> n >> m >> k;
for (int i = 1; i <= n; i ++ ) cin >> num[i];
while (m -- )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
dijkstra();
memset(st, false, sizeof st);
//计算每个节点的子树的奶牛
//升序遍历保证路径按照走最小的字典序
for (int ver = 1; ver <= n; ver ++ )
{
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] == dist[ver] + w[i] && !st[j])
{
st[j] = true;
g[ver].pb(j), g[j].pb(ver);
}
}
}
dfs(1, -1);
// for (int i = 1; i <= n; i ++ ) cout << sz[i] << endl;
cout << res << endl;
}
signed main()
{
quick_cin();
// cin >> T;
T = 1;
while (T -- )
{
solve();
}
return 0;
}
P4264 [USACO18FEB]Teleportation S
题意:
FarmerJohn正在正在搬粪,他要把粪从 a 地搬运到 b 地
他经过良心发现,决定在 x=0 的地方建造一个垃圾回收站,你的任务是确定一个最优的点 y ,使得他可以通过传输门或直接走到 b 地的所走路程最小。
题解:
如果当这个传送门的端点位于 y 的时候,最小的求出总代价,我们设为函数。
通过观察,我们可以发现,有一些点是永远都是不可能走传输门的。
当 时,
;
当 时,函数有三个交接点:
分别是:
1、 ,斜率为
;
2、 ,斜率为
;
3、 ,斜率为
;
所以可以选择用 map 映射存储 函数在交接点的斜率的变化,然后再按照 y 的顺序遍历就可以得到答案了。
具体代码如下:
#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#define int long long
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<LL, LL> PLL;
typedef pair<int, int> PII;
const int N = 200010, M = 100010;
const int MOD = 998244353;
const int INF = 0x7fffffff;
int n, m, T, k;
int res;
map<int, int> S;
int dx[] = {1, -1, 0, 0}, dy[] = {0, 0, 1, -1};
void solve()
{
int base = 0;
cin >> n;
for (int i = 1; i <= n; i ++ )
{
int a, b;
cin >> a >> b;
base += abs(a - b);
if (abs(a) >= abs(a - b)) continue;
S[b] += 2;
if ((a < b && a < 0) || (a >= b && a >= 0)) S[0] -- , S[2 * b] -- ;
if ((a < b && a >= 0) || (a >= b && a < 0)) S[2 * b - 2 * a] -- , S[2 * a] -- ;
}
res = base;
int y = -INF, k = 0;
map<int, int>::iterator it;
for (it = S.begin(); it != S.end(); it ++ )
{
int ny = it->first, tmp = it->second;
base += k * (ny - y);
y = ny;
k += tmp;
res = min(res, base);
}
cout << res << endl;
}
signed main()
{
quick_cin();
// cin >> T;
T = 1;
while (T -- )
{
solve();
}
return 0;
}
P4377 [USACO18OPEN] Talent Show G
题意:
有 n 个物品,每个物品有两个属性 (w, t) ,现在你要选出一些物品使得选出的物品的 w 属性的和大于等于m,并且使得选出的物品的 w 属性的和与 t 属性的和比值最大。
题解:
1、二分答案:
化简得到:
即
为了方便令 。
2、01背包
做一个大小为 的背包,重量大于等于
的,都记在
上。 最后只要看
是否大于0。 就可以解决重量至少为
的限制。
具体代码如下:
#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#define int long long
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<LL, LL> PLL;
typedef pair<int, int> PII;
const int N = 200010, M = 100010;
const int MOD = 998244353;
const int INF = 0x7fffffff;
const double esp = 1e-6;
int n, m, T, k;
int res;
double f[N];
int w[N];
double t[N], c[N];
int dx[] = {1, -1, 0, 0}, dy[] = {0, 0, 1, -1};
bool check(double mid)
{
for (int i = 1; i <= n; i ++ )
c[i] = t[i] - mid * w[i];
for (int i = 1; i <= m; i ++ )
f[i] = -INF;
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= 0; j -- )
{
if (j + w[i] >= m)
f[m] = max(f[m], f[j] + c[i]);
else
f[j + w[i]] = max(f[j + w[i]], f[j] + c[i]);
}
return f[m] >= 0;
}
void solve()
{
cin >> n >> m;
double l = 0, r = 0;
for (int i = 1; i <= n; i ++ )
{
cin >> w[i] >> t[i];
t[i] *= 1000;
r += t[i];
}
while (r - l > esp)
{
double mid = (l + r) / 2;
if (check(mid)) l = mid;
else r = mid;
}
cout << (int)l << endl;
}
signed main()
{
quick_cin();
// cin >> T;
T = 1;
while (T -- )
{
solve();
}
return 0;
}