「模拟赛」暑期集训CSP提高模拟4(7.21)
很祭的一次比赛,啥也不会。
题目列表:
A.White and Black
题意:
给你一颗树,树根为 1 ,初始所有点都是白色,每次询问给出一个点集,若把点集中的点全部染成黑色,问至少需要翻转多少个节点才能使整棵树全为白点。无解输出 -1。询问之间相互独立
翻转的定义为:将节点 u 及其子树中所有节点颜色翻转。
赛时分析:
赛时不会,想到了时间复杂度
以后模拟赛不能一个思路耗死,不能太高估自己,像这个题,性质也没那么难想,但自己完全就是没想着会有什么性质,一直在优化自己的 dp,看来以后要及时否定自己多换思路。
正解:
注:下文“染黑某个点”等说法皆指染黑以该点为根的子树。
思想:
我们枚举每个被染黑的点,设为
再枚举
计算答案
这便是我们的思想,不过较容易发现,时间复杂度为
暴力代码
#include<bits/stdc++.h>
#define mp make_pair
#define ll long long
using namespace std;
const int N = 2e5 + 10;
int n, Q, m, in[N];
int color[N], fa[N];
map<pair<int, int>, int>son;
signed main(){
// freopen("in.in", "r", stdin); freopen("a.out", "w", stdout);
scanf("%d%d", &n, &Q);
for(int i=2; i<=n; i++){
scanf("%d", &fa[i]);
son[mp(fa[i], ++son[mp(fa[i], 0)])] = i;
}
while(Q--)
{
scanf("%d", &m);
for(register int i=1; i<=m; i++){
scanf("%d", &in[i]);
color[in[i]] = 1;
}
int ans = 0;
for(int i=1; i<=m; i++){
if(!color[fa[in[i]]]) ans++;
for(int j=1; j<=son[mp(in[i], 0)]; j++){
if(!color[son[mp(in[i], j)]]) ans++;
}
}
printf("%d\n", ans);
for(register int i=1; i<=m; i++) color[in[i]] = 0;
}
return 0;
}
考虑优化:
时间复杂度多的那个
所以在枚举每个黑点的时候,我们可以直接加上该点儿子的数量。但这样就多加了黑儿子啊,按照我们的思路只加白儿子,但别忘了,黑儿子是被染黑的点,所以我们枚举所有黑点时总会枚举到这些多加的黑儿子,那么我们枚举时,判断枚举点的父亲节点是否为黑,若为父亲节点为白,则加一次操作,否则把减去一次操作,这样就把多加的黑儿子减去了。
成功优化到
最终代码:
#include<bits/stdc++.h>
#define mp make_pair
#define ll long long
using namespace std;
const int N = 2e5 + 10;
int n, Q, m, in[N];
int color[N], fa[N], son[N];
signed main(){
// freopen("in.in", "r", stdin); freopen("a.out", "w", stdout);
scanf("%d%d", &n, &Q);
for(int i=2; i<=n; i++){
scanf("%d", &fa[i]);
son[fa[i]]++;
}
while(Q--)
{
scanf("%d", &m);
for(register int i=1; i<=m; i++){
scanf("%d", &in[i]);
color[in[i]] = 1;
}
int ans = 0;
for(int i=1; i<=m; i++){
if(!color[fa[in[i]]]) ans++;
else ans--;
ans += son[in[i]];
}
printf("%d\n", ans);
for(register int i=1; i<=m; i++) color[in[i]] = 0;
}
return 0;
}
White and White
题意:
怀特有一个长度为
划分是将这个这个数列分成
使得这
Black and Black
题意:
布莱克现在有一个长度为
为了给以后的 dp 做更多准备,现在他想求出一个布莱克序列
- 序列长同样为
- 序列严格递增,即
; 。
给定一个序列,求是否是布莱克序列。
赛时分析:
其实赛时基本想到正解了,但由于在 T2 卡的时间过长,所以 T3 根本没敢仔细想即使是已经出现正解的思路,只是在最后半小时打完了 T3 和 T4 的暴力。
正解:
构造。我们先将
-
若
直接输出即可 -
时- 从头到尾扫一遍
,找到第一个 的情况,(由于是第一个,那么一定有找到的 )那么说明我们可以把 都减去 ,等价于整个 数组的总和减去了 ,那么总和就为 0 了; - 若未扫到,那再从尾到头再扫一遍,找到第一个
的情况(同理 ),这样我们把 全都加上 即可; - 还是找不到的话,那么说明无解,输出 -1。
- 从头到尾扫一遍
-
时,与上述相反操作即可。
code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5 + 10;
int n, a[N]; ll b[N], sum;
ll s[N];
inline void print(){
puts("Yes");
for(int i=1; i<=n; i++) printf("%lld ", b[i]);
}
int main(){
// freopen("in.in", "r", stdin); freopen("out.out", "w", stdout);
scanf("%d", &n);
for(int i=1; i<=n; i++){
scanf("%d", &a[i]);
s[i] = s[i-1] + a[i];
b[i] = i; sum += i * a[i];
}
if(sum == 0) print();
else if(sum < 0){
for(int i=n; i>=1; i--){
if(s[n] - s[i-1] > 0){
for(int j=i; j<=n; j++){
b[j] += -sum;
}
print();
return 0;
}
}
for(int i=1; i<=n; i++){
if(s[i] - s[0] < 0){
for(int j=1; j<=i; j++){
b[j] += sum;
}
print();return 0;
}
}
puts("No");
}
else{
for(int i=n; i>=1; i--){
if(s[n] - s[i-1] < 0){
for(int j=i; j<=n; j++)
b[j] += sum;
print();
return 0;
}
}
for(int i=1; i<=n; i++){
if(s[i] - s[0] > 0){
for(int j=1; j<=i; j++)
b[j] -= sum;
print();
return 0;
}
}
puts("No");
}
return 0;
}
Black and White
题意:
给你一颗树,边长均为1,初始所有点都是黑色,有以下两种操作:
C u
将点 的颜色反转,即黑变白,白变黑;G
询问距离最远的两个黑点的距离;
对于每一个操作 G,输出一个整数,表示最远的两个黑点的距离。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】