清北学堂考前综合刷题班第四场

A. 油箱

【问题描述】

\(n\) 个城市,每个城市都有加油站,有 \(m\) 条单向道路,距离为 \(x\) 的道路需要消耗 \(x\) 升的汽油。请问你的车辆可以携带的最小油箱容量,使得不限加油次数的情况下,无论你在哪个城市都可以到达任意的城市。

【输入格式】

第一行两个正整数 \(n\),\(m\)

接下来 \(m\) 行,每行三个正整数 \(x,y,z\) 表示一条 \(x\)\(y\) 的有向边,边权为 \(z\)

【输出格式】

输出符合条件的最小油箱容量,如果无法满足输出 \(−1\)

【样例输入】

3 5
1 2 1
1 3 2
2 3 5
3 1 4
2 3 1

【样例输出】

‮4

【样例解释】

油箱容量为 \(4\) 时, \(1→2,1→3,3→1,2→3\) 道路可以通行,此时满足无论你在哪个城市都可以到达任意的城市。

【数据规模与约定】

\(20%\) 的数据 \(n≤5,m≤20\)

\(40%\) 的数据 \(n≤50,m≤200\)

\(60%\) 的数据 \(n≤5×10^2,m≤2×10^3\)

100%100% 的数据 \(n≤5×10^4,m≤2×10^9\) 保证 \(1≤z≤10^9\)


这道题我用了一个很神奇的操作完成了。

首先,这道题我们使用二分。判断有无解。连接所有边权小于等于\(mid\)的边,判断所有点是否互相可达。

实际上如果所有点都能到达\(1\),并且\(1\)能到达所有点,则说明所有点互相可达。

本人的做法如下:

判断所有点是否在同一连通分量。如果是,则进行下一步;

统计所有点的入度和出度,如果所有点的入度和出度都不为0,该联通块具可达性。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
using namespace std;
const int SIZE = 50000 + 10, INF = 1 << 30;
struct Edge
{
	int u, v, w;
	bool operator < (const Edge& lhs)
	{
		return w > lhs.w;
	}
} e[SIZE << 2]; 
vector <int> G[SIZE];
bool judge[SIZE] = {};
int n, m, deg[SIZE] = {}, out[SIZE] = {}, fa[SIZE];
int get(int x)
{
	if(fa[x] == x) return x;
	return fa[x] = get(fa[x]);
}
void dfs(int u)
{
	judge[u] = true;
	for(int i = 0; i < G[u].size(); ++ i)
	{
		int v = G[u][i];
		if(!judge[v]) dfs(v);
	} 
	return;
}
bool valid(int x)
{
	memset(out, 0, sizeof(out));
	memset(deg, 0, sizeof(deg));
	for(int i = 1; i <= n; ++ i) fa[i] = i; 
	for(int i = 0; i < m; ++ i) 
	{
		if(e[i].w <= x && e[i].u != e[i].v) 
		{
			++ deg[e[i].v];
			++ out[e[i].u];
			fa[get(e[i].v)] = get(e[i].u);
		}
	}
	for(int i = 1; i <= n; ++ i) if(!deg[i] || !out[i]) return false;
	get(1);
	for(int i = 2; i <= n; ++ i) 
	{
		if(get(i) != fa[1]) return false; 
	}
	return true;
}
int main()
{
	scanf("%d %d", &n, &m);
	for(int i = 0; i < m; ++ i)
	{	
		scanf("%d %d %d", &e[i].u, &e[i].v, &e[i].w);
		int u = e[i].u, v = e[i].v, w = e[i].w;
		if(u != v)
		{
			G[u].push_back(v);
			++ out[u];
			++ deg[v];
		}
	}
	dfs(1);
	for(int i = 1; i <= n; ++ i) 
		if(!judge[i])
		{
			puts("-1");
			return 0;
		}
	sort(e, e + m);
	if(n == 1) puts("0");	
	else
	{
		for(int i = 1; i <= n; ++ i) 
			if(!deg[i] || !out[i])
			{
				puts("-1");
				return 0;
			} 
		int L = INF, R = 0, mid;
		for(int i = 0; i < m; ++ i) L = min(L, e[i].w), R = max(R, e[i].w);
		while(L < R)
		{
			mid = L + ((R - L) >> 1);
			if(valid(mid)) R = mid;
			else L = mid + 1;
		}
		printf("%d\n", R);
	}
	return 0;
}

B. 求和

【问题描述】

给定一颗\(n\)个点组成的树,根节点为\(1\)号节点,每个点上有两个权值\(a_i, b_i\)。共有\(m\)次询问,每次询问给出两个正整数\(x, y\),从\(x\)\(y\)的路径设为\(t_1,t_2,…,t_k\) 要求输出

\[∑_{1≤i<j≤k}a_{t_i}∗b_{t_j} \]

【输入格式】

第一行两个正整数\(n,m\)

第二行\(n-1\)个数 \(f_2,f_3,…,f_n\),表示点i的父亲(\(f_i< i\))

接下来两行,每行\(n\)个数 分别表示\(a_i,b_i\)

接下来\(m\)行,每行两个正整数\(x_i,y_i\) ,表示第\(i\)次询问

【输出格式】

对于每个询问操作,输出答案。

【样例输入】

5 4
1 2 3 4
1 2 3 4 5
1 2 3 4 5
1 2
1 3
1 4
1 5

【样例输出】

2
11
35
85

【数据规模与约定】

20% n,m<=100

40% n,m<=2000

另20% 保证 ai=bi

另20% 保证fi=i-1, x<=y

100% n,m<=100000 保证1<=ai,bi<=10000


这道题我使用树上倍增。

当然,我们首先先考虑维护\(suma[u]\)\(sumb[u]\)代表从该点到根节点的所有权值和。发现我们还可以维护\(prob_a[u]\)\(prob_b[u]\)用以计算从该点\(u\)到根节点的所有\(a_i*b_j\)。然后分别维护。

对于\(u,v\),答案即为:

\[prob_a[u]+prob_b[v]-prob_a[lca]-prob_b[lca]-(suma[u]-sum[lca])*sumb[fa[lca]]-(sumb[v]-sumb[lca])*suma[fa[lca]]+(suma[u]-sum[lca])*(sumb[v]-sumb[lca]) \]

这道题启发我可以利用维护序列上的操作维护树上较难处理的操作。

最后,别忘开long long!

#include<iostream>
#include<cstring>
#include<vector>
#include<cstdio> 
#include<cmath>
using namespace std;
const int SIZE = 100000 + 5;
vector <int> G[SIZE];
int n, m, t, a[SIZE], b[SIZE], dep[SIZE], F[SIZE][40];
long long sa[SIZE], sb[SIZE], prob_a[SIZE], prob_b[SIZE];
void prework()
{
	memset(sa, 0, sizeof(sa));
	memset(sb, 0, sizeof(sb));
	memset(prob_a, 0, sizeof(prob_a));
	memset(prob_b, 0, sizeof(prob_b));
	memset(dep, 0, sizeof(dep));
	memset(F, 0, sizeof(F)); 
	return;
}
void dfs(int u, int Fa)
{
	sa[u] = a[u] + sa[Fa], sb[u] = b[u] + sb[Fa];
	prob_a[u] = a[u] * sb[Fa] + prob_a[Fa], prob_b[u] = b[u] * sa[Fa] + prob_b[Fa];
	dep[u] = dep[Fa] + 1;
	F[u][0] = Fa;
	for(int i = 1; i <= t; ++ i) F[u][i] = F[F[u][i - 1]][i - 1];
	int v;
	for(int i = 0; i < G[u].size(); ++ i)
	{
		v = G[u][i];
		if(v != Fa) dfs(v, u);	
	}
	return;
}
int LCA(int x, int y)
{
	if(dep[x] > dep[y]) swap(x, y);
	for(int i = t; i >= 0; -- i) 
		if(dep[F[y][i]] >= dep[x]) y = F[y][i];
	if(x == y) return x;
	for(int i = t; i >= 0; -- i)
	{
		if(F[x][i] != F[y][i])
		{
			x = F[x][i];
			y = F[y][i];
		}
	}
	return F[x][0];
}
long long query(int x, int y)
{
	int lca = LCA(x, y), fa = F[lca][0];
	return prob_a[x] + prob_b[y] - (sa[x] - sa[fa]) * sb[fa] - (sb[y] - sb[fa]) * sa[fa] - prob_a[fa] - prob_b[fa] + (sb[y] - sb[lca]) * (sa[x] - sa[lca]);
}
int main()
{
	scanf("%d %d", &n, &m);
	t = log(n) / log(2);
	for(int i = 1; i <= n; ++ i) G[i].clear();
	for(int u = 2; u <= n; ++ u)
	{
		int v;
		scanf("%d", &v);
		G[u].push_back(v), G[v].push_back(u);
	}
	for(int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
	for(int i = 1; i <= n; ++ i) scanf("%d", &b[i]); 
	prework();
	dfs(1, 0);
	int x, y;
	while(m --)
	{
		scanf("%d %d", &x, &y);
		printf("%lld\n", query(x, y));		
	}
	return 0;
}

C. 染色

【问题描述】

一排有n个格子,染成m种颜色,相邻格子颜色不能相同。此外,允许一个长度不超过k的区间染成相同的颜色。求最小代价。

【输入格式】

第一行包含三个正整数n, m, k表示格子数量、颜色数量、区间长度。

接下来的n行,每行有m个正整数cost i,j 表示将第i个格子染成颜色j需要付出的代价。

由于输入数据过大,可能需要使用快速读入

inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0'|ch>'9')ch= getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch= getchar ();
    return x;
}

【输出格式】

输出一个整数,表示合法方案的最小代价。

【样例输入】

5 3 3
1 5 6
1 2 3
9 1 9
9 1 9
1 2 3

【样例输出】

6

【样例说明】

颜色分别为1 2 2 2 1

【数据规模与约定】

15% n, m<=10 k<=n

40% n, m<=100 k<=10

60% n, m<=300 k<=n

另15% n, m<=2000 k=1

100% n, m<=2000 k<=n cost<=10000


题意中有一句话:一个长度不超过k的区间染成相同的颜色,指的是一个(考场期间我就以为是多个)。

首先\(k=1\),互不相同。DP带走;

其次,我们可以枚举每一个长度不超过\(k\)的子串,分别计算。

接着刚才的想法,我们可以不妨优化一下:预处理DP,接着单调队列。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath> 
#include<queue>
using namespace std;
const int N = 2000 + 5, M = 2000 + 5, INF = 1 << 30;
int n, m, k, ans = 0, cost[N][M], s[N], dpL[N][M], dpR[N][M], dp[M]; 
deque <int> Q;
inline void read(int &x)
{
	bool mark = false;
	char ch = getchar();
	for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') mark = true;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0';
	if(mark) x = -x;
	return;
} 
void prework()
{
	memset(dpL, 0x3f, sizeof(dpL));
	memset(dpR, 0x3f, sizeof(dpR));
	for(int i = 1; i <= m; ++ i) dpL[0][i] = dpR[n + 1][i] = 0;
	int val; 
	for(int i = 1; i <= n; ++ i)
	{
		val = INF;
		for(int j = 1; j <= m; ++ j) dp[j] = dpL[i - 1][j] + cost[i][j];
		for(int j = 1; j < m; ++ j) 
		{
			val = min(val, dp[j]);
			dpL[i][j + 1] = min(dpL[i][j + 1], val);
		}
		val = INF;
		for(int j = m; j > 1; -- j)
		{
			val = min(val, dp[j]);
			dpL[i][j - 1] = min(dpL[i][j - 1], val);
		}
	}

	for(int i = n; i > 0; -- i)
	{
		val = INF;
		for(int j = 1; j <= m; ++ j) dp[j] = dpR[i + 1][j] + cost[i][j];
		for(int j = 1; j < m; ++ j) 
		{
			val = min(val, dp[j]);
			dpR[i][j + 1] = min(dpR[i][j + 1], val);
		}
		val = INF;
		for(int j = m; j > 1; -- j)
		{
			val = min(val, dp[j]);
			dpR[i][j - 1] = min(dpR[i][j - 1], val);
		}
	}
	return;
}
int main()
{
	read(n), read(m), read(k);
	for(int i = 1; i <= n; ++ i)
		for(int j = 1; j <= m; ++ j) read(cost[i][j]);
	
	prework();
	ans = INF;
	for(int i = 1; i <= m; ++ i)
	{
		memset(s, 0, sizeof(s));
		for(int j = 1; j <= n; ++ j) s[j] = s[j - 1] + cost[j][i];
		Q.clear();
		for(int j = 0; j <= n; ++ j)
		{
			while(Q.size() && j - Q.front() > k) Q.pop_front();
			if(Q.size()) ans = min(ans, dpR[j + 1][i] + s[j] - s[Q.front()] + dpL[Q.front()][i]);
			while(Q.size() && dpL[Q.back()][i] - s[Q.back()] >= dpL[j][i] - s[j]) Q.pop_back();
			Q.push_back(j);
		}
	}
	printf("%d\n", ans);
	return 0;
} 

D. 数字

【问题描述】

给出n个好数和m个坏数,求包含所有好数但不包含任何坏数,并且只由1-4组成的数中,各位数字之和最小值是多少。(好数和坏数均为k位数,且由1-4组成)

【输入格式】

第一行包含三个正整数n, m, k

接下来n行包含每行1个正整数,表示好数

接下来m行包含每行1个正整数,表示坏数

【输出格式】

输出一个正整数,表示最小的值。输入数据保证一定存在解。

【样例输入】

3 3 4
1111
1222
2333
1122
1133
3122

【样例输出】

20

【样例说明】

12223331111

【数据规模与约定】

20% n<=5 m=0 k=2

30% n<=10 m<=10 k<=5

40% n<=17 m<=20 k<=5

50% n<=18 m<=20 k<=5

60% n<=19 m<=20 k<=5

70% n<=20 m<=20 k<=5

100% n<=20 m<=10000 k<=8


题目好评,思维难题。

一看数据规模,再看好数的定义,第一想法就是旅行商问题,不过状压只是一个外套罢了啊。

转移过程中花费貌似挺难想的。我们把每一个\(k\)位数看作一个节点,花费即为节点距离。跑最短路。

举个例子:\(1234\)可以向\(2341\)\(2342\)\(2343\)\(2344\)(这四个数如果都不是不满足题意的数据)连边。

这些点压缩为一个数,四进制。注意常数因子给程序的影响

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<cmath>
#include<queue>
#define pii pair <int, int>
using namespace std;
const int N = 20 + 5, M = 10000 + 5, SIZE = 1 << 20, INF = 1 << 30;
priority_queue <pii> Q; 
string Good[N], Bad[M];
bool vis[SIZE], book[SIZE];
int n, m, k, st[N], dis[SIZE], d[N][N], dp[N][SIZE]; 
void Dijkstra(int S)
{
	while(Q.size()) Q.pop();
	memset(dis, 0x3f, sizeof(dis));
	memset(vis, 0, sizeof(vis));
	dis[S] = 0;
	Q.push(make_pair(0, S));
	while(Q.size())
	{
		int u = Q.top().second;
		Q.pop();
		if(vis[u]) continue;
		vis[u] = true;
		for(int op = 0; op < 4; ++ op)
		{
			int v = ((u % (1 << (2 * k - 2))) << 2) + op, w = op + 1;
			if(book[v]) continue;
			if(dis[v] > dis[u] + w)
			{
				dis[v] = dis[u] + w;
				Q.push(make_pair(-dis[v], v));
			}
		}
	}
	return;
}
void prework()
{
	memset(st, 0, sizeof(st)), memset(book, false, sizeof(book));
	for(int i = 1; i <= n; ++ i)
	{
		for(int j = 0; j < k; ++ j)
		{
			int tmp = Good[i][j] - '0';
			st[i] = st[i] * 4 + tmp - 1;
		}
	}
	int x;
	for(int i = 1; i <= m; ++ i)
	{
		x = 0;
		for(int j = 0; j < k; ++ j)
		{
			int tmp = Bad[i][j] - '0';
			x = x * 4 + tmp - 1;
		}
		book[x] = true;
	}
	memset(d, 0x3f, sizeof(d));
	for(int i = 1; i <= n; ++ i)
	{
		Dijkstra(st[i]);
		for(int j = 1; j <= n; ++ j) d[i][j] = dis[st[j]];
	}
	return;
}
int compute(int x) 
{
	int res = 0;
	for(int i = 0; i < k; ++ i) res += Good[x][i] - '0';
	return res;
}
int main()
{
	scanf("%d %d %d", &n, &m, &k);
	for(int i = 1; i <= n; ++ i) cin >> Good[i];
	for(int i = 1; i <= m; ++ i) cin >> Bad[i];
	prework();
	memset(dp, 0x3f, sizeof(dp));
	for(int i = 0; i < n; ++ i) dp[i + 1][1 << i] = compute(i + 1);
	for(int i = 1; i < 1 << n; ++ i)
	{
		for(int j = 1; j <= n; ++ j)
		{
			if(i & (1 << j - 1))
			{
				int pre_mask = i ^ (1 << j - 1);
				for(int p = 1; p <= n; ++ p)
				{
					if(pre_mask & (1 << p - 1))
					{
						dp[j][i] = min(dp[j][i], dp[p][pre_mask] + d[p][j]);
					}
				}
			}
		}
	}
	int ans = INF;
	for(int i = 1; i <= n; ++ i) ans = min(ans, dp[i][(1 << n) - 1]);
	printf("%d\n", ans);
	return 0;
}
posted @ 2020-10-30 22:51  大秦帝国  阅读(97)  评论(0)    收藏  举报