Codeforces Round #695 (Div. 2)(A - E)

这次的D题和E题真的很妙啊~~ (都是比赛结束以后学的)

A题

题目大意
有 n 个数字显示器,最初每个显示器都显示相同的数字,并且从 0 到 9 不断滚动。你可以选择在合适的时候暂停其中一个,接下来与其相邻的显示器滚动一个数字后依次暂停。如果显示器 x 在显示数字 5 的时候暂停,与 x 相邻的两个显示器a 和 b 会滚到 6 并且暂停。a 和 b 暂停后,a 的另一侧的显示器会滚动一个数字 7 后暂停… 依次类推,直到所有显示器暂停。
问:这 n 个显示器都暂停滚动后能显示最大的数字是什么。

思路:我们在第二个显示器显示 8 的时候暂停它会得到最大的数字989876…
AC代码


#include <bits/stdc++.h>
#include <vector>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <stack>
#define ll long long
#define chushi(a, b) memset(a, b, sizeof(a))
const double eps = 1e-8;
const int INF = 1e9;
const ll mod = 1e16;
const int maxn = 2e5 + 5;
using namespace std;

int main(){
	
	std::ios::sync_with_stdio(false);
	
	int ncase;
    cin >> ncase;
    while(ncase--){
    	int n;
    	cin >> n;
    	cout << 9;
    	for(int i = 2 ; i <= n; i++){
    		cout << (i + 6) % 10;
		}
		cout << endl;
		
    }
	
	return 0;
}

B题

题目大意
有 n 个数字,可以选择更改一个位置的数,求更改完之后山峰和山谷的和最小

思路:可以发现,如果我们把一个数改成与其相邻的两个数其中一个,可能去掉的山峰和山谷数可能是 0 、1、2、3
所以我们不需要知道到底是谁把去掉了几个山峰和山谷,我们只需要知道最后的结果是什么就好
实现方法:先统计出山峰和山谷的总数sum,如果要修改一个数字(与其相邻的两个数其中一个),要先统计出它原来对结果的贡献 x,再统计出修改过以后对结果的贡献 num,修改后的 sum’ = sum - x + num,取最小的一个 sum’ 即可

AC代码

#include <bits/stdc++.h>
#include <vector>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <stack>
#define ll long long
#define chushi(a, b) memset(a, b, sizeof(a))
const double eps = 1e-8;
const int INF = 1e9;
const ll mod = 1e16;
const int maxn = 3e5 + 5;
using namespace std;

int a[maxn];

int main(){
	
	std::ios::sync_with_stdio(false);
	
	int ncase;
    cin >> ncase;
    while(ncase--){
    	int n;
    	cin >> n;
    	for(int i = 1; i <= n; i++) cin >> a[i];
		
		ll res = 0;
		
		for(int i = 2; i < n; i++){	// 统计sum
			if(a[i] < a[i-1] && a[i] < a[i+1]) res++;
			if(a[i] > a[i-1] && a[i] > a[i+1]) res++;
		}
		
		ll f = res;
		for(int i = 1; i <= n; i++){
			int x = 0;
			for(int j = i-1; j <= i+1; j++){	// 统计 x
				if(j > 1 && j < n){
					if(a[j] < a[j-1] && a[j] < a[j+1]) x++;
					if(a[j] > a[j-1] && a[j] > a[j+1]) x++;
				}
			}
			int tmp = a[i], num = 0;
			a[i] = a[i-1];	// 修改
			for(int j = i-1; j <= i+1; j++){	// 统计 num
				if(j > 1 && j < n){
					if(a[j] < a[j-1] && a[j] < a[j+1]) num++;
					if(a[j] > a[j-1] && a[j] > a[j+1]) num++;
				}
			}
			res = min(res, f + num - x);	// 取最小
			a[i] = a[i+1]; num = 0;	// 修改
			for(int j = i-1; j <= i+1; j++){	// 统计 num
				if(j > 1 && j < n){
					if(a[j] < a[j-1] && a[j] < a[j+1]) num++;
					if(a[j] > a[j-1] && a[j] > a[j+1]) num++;
				}
			}
			res = min(res, f + num - x);	// 取最小
			a[i] = tmp;	// 还原修改,不然会影响后边的修改
		}
		
		cout << res << endl;
		
    }
	
	return 0;
}

C题

题目大意
给你三个袋子。每个袋子包含一个非空的多组数字。您可以对这些袋子执行许多操作。一次操作可以任意选择两个非空袋,每个袋中选择一个号码。假设你从第一个袋子中选择了a号从第二个袋子中选择了b号。然后,把b从第二个袋子里拿出来,把a换成第一个袋子里的a−b。注意,如果这些数字出现多次,那么您应该只删除/替换一个。
您必须以这样一种方式来执行这些操作,即您在一个袋子中恰好保留一个数字(另外两个袋子是空的)。可以看出,您最终总是可以应用这些操作来接收这样的配置。在所有这些构型中,找出最后剩余数最大的那个构型。
有道翻译的结果

思路:
这里我们很容易想到的一种方法,如图:
在这里插入图片描述
很明显,这样操作过以后,剩下的数字
num = x - (minb - A) - (mina - B) = x + A + B - mina - minb = suma + sumb + sumc - 2 * mina - 2 * sumb
很明显,我们在三个袋子中,选两个袋子分别损失一个最小的数就OK
但是,这样得到的一定是最优的结果吗?
看样例
4 4 1
1 2 3 4
10000
10000

对于这一组样例,我看可以发现,按照刚刚的思路,我们得到的结果是 8,很明显这不是最优解
这里我们可以发现,当 minb > suma 时,我们选择损失掉 minb ,不如直接损失掉suma
在这里插入图片描述
如图,最后的结果就是 x - (mina - A + B) = sumc + sumb - suma
综上,我们的最优结果就是 min(suma + sumb + sumc - 2 * mina - 2 * sumb,sumc + sumb - suma
AC代码


#include <bits/stdc++.h>
#include <vector>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <stack>
#define ll long long
#define chushi(a, b) memset(a, b, sizeof(a))
const double eps = 1e-8;
const int INF = 1e9;
const ll mod = 1e16;
const int maxn = 1e6 + 5;
using namespace std;

ll a[maxn];
ll b[maxn];
ll c[maxn];

int main(){
	
	std::ios::sync_with_stdio(false);
	
	int na, nb, nc;
	cin >> na >> nb >> nc;
	for(int i = 1; i <= na; i++) cin >> a[i];
	for(int i = 1; i <= nb; i++) cin >> b[i];
	for(int i = 1; i <= nc; i++) cin >> c[i];
	
	sort(a+1, a+1+na);
	sort(b+1, b+1+nb);
	sort(c+1, c+1+nc);
	
	ll suma = 0, sumb = 0, sumc = 0;
	for(int i = 1; i <= na; i++) suma += a[i];
	for(int i = 1; i <= nb; i++) sumb += b[i];
	for(int i = 1; i <= nc; i++) sumc += c[i];
	
	
	ll res = suma + sumb + sumc - 2 * min(b[1] + c[1], min(a[1] + b[1], a[1] + c[1]));
	res = max(suma + sumb - sumc, res);
	res = max(suma + sumc - sumb, res);
	res = max(sumb + sumc - suma, res);
	
	cout << res << endl;
	
	return 0;
}

D题

题目大意
定义一条好的路径,当且仅当从任意点出发之后恰好经过了k 次移动,定义这条路径的权值为经过点权值ai的总和,进行 q 次修改,每次将第 ak改为x, 询问此时所有‘好’路径的权值总和

思路:
看大佬的博客就好,链接在这 ~~~

这个dp[i][j] 一语双关就很棒,并且下面这段代码统计每个点所在的总路径数真的很妙啊

for(int i = 1; i <= n; i++){
		for(int j = 0; j <= k; j++){
			c[i] = (c[i] + (dp[i][j] * dp[i][k-j]) % mod) % mod;
		}
	}

(走了 j 步,当前在 i 点的路径数) * (当前在 i 点,走 k - j 步可以到任点的路径数 ) 对 j = 【 0,n】求和的结果就是 i 这个点所在的总路径数真的很妙

AC代码


#include <bits/stdc++.h>
#include <vector>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <stack>
#define ll long long
#define chushi(a, b) memset(a, b, sizeof(a))
#define endl "\n"
const double eps = 1e-8;
const ll INF=0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 1e5 + 5;
const int N=1005;
using namespace std;

ll a[5005];
ll dp[5005][5005] = {0};	// dp[i][j] 走了 j 步,当前在 i 点的路径数 
ll tot[5005] = {0};			// dp[i][j] 当前在 i 点,走 j 步可以到任点的路径数 
ll c[5005] = {0};

int main() {

	int n, k, q;
	cin >> n >> k >> q;
	for(int i = 1; i <= n; i++) cin >> a[i];
	
	for(int i = 1; i <= n; i++) dp[i][0] = 1;
	for(int i = 1; i <= k; i++){
		for(int j = 1; j <= n; j++){
			dp[j][i] = dp[j-1][i-1] + dp[j+1][i-1];
			dp[j][i] %= mod;
		}
	}
	
	for(int i = 1; i <= n; i++){
		for(int j = 0; j <= k; j++){
			c[i] = (c[i] + (dp[i][j] * dp[i][k-j]) % mod) % mod;
		}
	}
	
	ll res = 0;
	for(int i = 1; i <= n; i++) res = (res + a[i] * c[i] % mod) % mod;
	
	while(q--){
		ll x, num;
		cin >> x >> num;
		
		res = (res - a[x] * c[x] % mod + mod) % mod;
		a[x] = num;
		res = (res + a[x] * c[x] % mod) % mod;
		
		cout << res << endl;
	}
	
	return 0;

}

E题

题目大意
给定一棵 n 个结点的树,每个点有一个权值ai,问有多少个点 u 满足从 u出发到其他所有点的路径中都不存在两个权值相同的点。

思路:
我们可以发现,计算合法的点是比较麻烦的,我们可以选择去统计不合法的点
这个题画图来看的话就很方便
在这里插入图片描述
在这里插入图片描述

可以发现,当两个点权值是相等的时:
如果这两个点的权值有祖先关系,那么显然可能合法的点只能是在 v 所在的那棵以u 的某个子节点为根的子树中的而又不在以 v 为根的子树中的点
如果这两个点的权值无祖先关系,那么可以确定在以 u 为根的子树中和以 v 为根节点的子树中的点不合法的
具体实现:树上差分,用 fi = 0 表示这个点是合法的,最后统计合法点的个数就ok
看代码~~

AC代码


#include <bits/stdc++.h>
#include <vector>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <stack>
#define ll long long
#define chushi(a, b) memset(a, b, sizeof(a))
#define endl "\n"
const double eps = 1e-8;
const ll INF=0x3f3f3f3f;
const int mod = 20000311;
const int maxn = 2e5 + 5;
const int N=1005;
using namespace std;

int f[maxn] = {0}, a[maxn];
vector<int> ma[maxn];
map<int, int> num, tot;

void dfs(int u, int fa){
	int tmpp = tot[a[u]];
	tot[a[u]]++;
	
	int len = ma[u].size();
	for(int i = 0; i < len; i++){
		int v = ma[u][i];
		if(v == fa) continue;
		int tmp = tot[a[u]];	// 记录 a[u] 当前出现的次数 
		dfs(v, u);
		if(tmp != tot[a[u]]){	// a[u] 出现的次数改变,则 
			f[1]++;		// 在 v 的子树中出现了权值为 a[u]的点,则 
			f[v]--;		// 节点 u 的子树中,除 v子树以外的其他子树都是非法的 
		}
	}
	
	if(tot[a[u]] - tmpp != num[a[u]]) f[u]++;	// 在其他子树里边也有权值为 a[u],则该点的子树都是非法的  
}

int res = 0;
void getsum(int u, int fa, int sum){	// 遍历维护差分 
	if(!sum) res++;		// 统计合法点个数 
	int len = ma[u].size();
	for(int i = 0; i < len; i++){
		int v = ma[u][i];
		if(v == fa) continue;
		getsum(v, u, sum + f[v]);
	}
}

int main(){
	
	int n;
	cin >> n;
	for(int i = 1; i <= n; i++) cin >> a[i];
	int u, v;
	for(int i = 1; i < n; i++){
		cin >> u >> v;
		ma[u].push_back(v);
		ma[v].push_back(u);
	}
	
	for(int i = 1; i <= n; i++) num[a[i]]++;  // 统计各个权值出现的次数 
	
	dfs(1, 0);
	
	getsum(1, 0, f[1]);
	
	cout << res << endl;
	
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值