ZUFE训练清题记录Ⅰ

P5122 [USACO18DEC]Fine Dining G

题意:

有 n 个点 m 条无向边,终点为点 n 。有 k 个特殊点,给出每个点的位置,以及一个值 val 。当你到达一个点,你的路径长度会减少 val 。路径长度最多只能减一次。
问每一个从 出发到 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 的时候,最小的求出总代价,我们设为函数f(y)

通过观察,我们可以发现,有一些点是永远都是不可能走传输门的。

当 \left |a \right | \geqslant \left | a-b \right | 时, f(y)=\left | a-b \right | ;

\left |a \right | < \left | a-b \right | 时,函数有三个交接点:

分别是:

1、 b-\left | a-b \right | +\left | a \right | ,斜率为 -1 ;

2、 b ,斜率为 2 ;

3、 b+\left | a-b \right |-\left | a \right | ,斜率为 -1 ;

所以可以选择用 map 映射存储 f(y) 函数在交接点的斜率的变化,然后再按照 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、二分答案:

\frac{\sum t_{i}}{\sum w_{i}}\geq mid

化简得到:

\sum t_{i}-mid\times \sum w_{i}\geq 0

\sum \left ( t_{i}-mid\times w_{i}\right )\geq 0

为了方便令c_{i} = t_{i}-mid\times w_{i} 。

2、01背包

做一个大小为 m 的背包,重量大于等于 m 的,都记在 f[m] 上。 最后只要看 f[m] 是否大于0。 就可以解决重量至少为 m 的限制。

具体代码如下:

#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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值