这次的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;
}