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


    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
    ", 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
    ", 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
    ", 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
    ", ans);
    	return 0;
    }
    
  • 相关阅读:
    渐变
    阴影
    html+css
    background用法
    语言特点
    h5c3介绍
    js的组成
    第九章 查找文件或者命令
    第八章 查看文件内容命令
    第七章 文件管理之基础命令
  • 原文地址:https://www.cnblogs.com/zach20040914/p/13904709.html
Copyright © 2020-2023  润新知