[43] (CSP 集训) CSP-S 模拟 10
B.清扫
考虑从叶子节点往上推
首先可以发现的几个性质
- 子树内需要消除的数,要么通过子树根节点 “发送” 到上面(只经过子树内一个叶节点),要么通过自己的叶节点解决
- 对于子树内既不是根也不是叶节点的节点,节点上的值只能由这一支路的叶节点消除,所以如果他节点上的值和下面节点 “发送” 上来的值不相等时,无解
然后考虑怎么去计算子树根节点向上的 “发送” 数量
设这个数量为 \(k\),子树各支路 “发送” 到子树根节点的数量总和为 \(s\),子树根节点的权值为 \(a\),可以发现
- 子树内解决会为 \(s\) 带来 \(-2\) 的贡献
- “发送” 出去会为 \(s\) 带来 \(-1\) 的贡献
- 无论何种操作,每次操作总会给 \(a\) 带来 \(-1\) 的贡献
因此:
\[k+2(a-k)=s
\]
由此可以唯一确定 \(k\),将这个值继续往上传即可
例图
这是一个 \(k=0\) 的图
需要注意的
- 由题有 \(0\le k\le a\),否则无解
- 我们在上述式子中只判断了数量关系,但实际上,由于子树内必须要选择不同的叶子配对,因此可能会出现有叶子无法使用的情况,此时是无解的
这种情况的判断方式:看看子节点里是不是有比这个点自己还大的,有就报告无解
- 注意 \(n=2\) 的情况需要特判
#include<bits/stdc++.h>
using namespace std;
int n;
int a[100001];
vector<int>e[100001];
int degree[100001];
bool flag=false;
int dfs(int now,int last){
int res=0,cnt=0,maxn=0;
for(int i:e[now]){
if(i!=last){
cnt++;
int r=dfs(i,now);
res+=r;maxn=max(maxn,r);
if(flag) return 0;
}
}
if(cnt==0){ //叶子
return a[now];
}
if(cnt==1){ //既不是根也不是叶节点,直接往上传
if(res!=a[now]){
flag=true;
return 0;
}
return res;
}
if(res<a[now] or res>2*a[now]){ //判断解是否合法
flag=true;
return 0;
}
if(maxn>a[now]){ //判断是不是消完了
flag=true;
return 0;
}
return 2*a[now]-res; //继续传答案
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
}
if(n==2){
cout<<(a[1]==a[2]?"YES":"NO")<<'\n';
return 0;
}
for(int i=1;i<=n-1;++i){
int x,y;scanf("%d %d",&x,&y);
e[x].push_back(y);
e[y].push_back(x);
degree[x]++;degree[y]++;
}
for(int i=1;i<=n;++i){
if(degree[i]!=1){ //不能从叶子开始搜
if(dfs(i,0)){ //判断到整颗树根节点是不是全消完了
flag=true;
}
break;
}
}
cout<<(flag?"NO":"YES");
}
C.购物
怎么这么签,我要急了
发现可以直接对原数组排序
假设上一次操作造成了一个连通块(这个连通块的右端点一定是 \(sum\))
本次更新的时候判断一下:
-
本次更新的区间与上一个区间有交
-
本次区间与上一个区间中间有断点
这两种情况都是可以直接计算出新增贡献的
然后因为排序了,所以你的值是越来越大的,不用担心区间跑到左边的某个位置(包括之前没填上的缝隙),所以只看这个右端点就能做完
又写了一个啥用没有的头文件,还是用一下
感觉现在头文件一直在写底层的东西
但是不写总感觉不太舒服
#include<bits/stdc++.h>
#include<io.h>
#include<array.h>
using namespace std;
using namespace hdk;
#define int long long
array<int>a;
int sum=0,ans=0;
signed main(){
a.read_array(fastio::read(),sort(a));
for(array_iter i:a){
int tmp=(i+1)/2;
if(tmp>sum) ans+=sum+i-tmp+1;
else ans+=i;
sum+=i;
}
cout<<ans;
}
D.ants
回滚莫队板子题
这道题莫队是显然的,但是该怎么莫队
可以对值域维护信息,维护当其为左/右端点时,排列能扩展到的最大值
然后发现对于值域上的 \(i\),有 \(l_i=l_{i-1}+1\)(放到 \(i-1\) 后面),\(r\) 是同理的,这样你就能直接对着原序列扩展值域信息
不好删除,回滚,注意到 \(n,m\) 同阶,回滚别用 memcpy,开个栈弹一下就好了
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[200001];
int len,locate[200001];
struct ques{
int l,r;
int id;
bool operator <(const ques &A)const{
if(locate[l]==locate[A.l]) return r<A.r;
return locate[l]<locate[A.l];
}
}q[200001];
void prework(){
len=sqrt(n);
for(int i=1;i<=n;++i){
locate[i]=i/len+(i%len!=0);
}
}
int res=0,scroll_ans=0;
#define rbor(x) min(n,(locate[(x)])*len)
int llen[200001],rlen[200001];
stack<pair<int,int>> scroll_l,scroll_r;
void add(int x,int flag){
llen[a[x]]=llen[a[x]-1]+1;
rlen[a[x]]=rlen[a[x]+1]+1;
int ret=llen[a[x]]+rlen[a[x]]-1;
res=max(res,ret);
if(flag){
scroll_l.push({a[x]+rlen[a[x]]-1,llen[a[x]+rlen[a[x]]-1]});
scroll_r.push({a[x]-llen[a[x]]+1,rlen[a[x]-llen[a[x]]+1]});
}
llen[a[x]+rlen[a[x]]-1]=rlen[a[x]-llen[a[x]]+1]=ret;
}
void scroll_back(int l,int r){
res=scroll_ans;
while(!scroll_l.empty()){
llen[scroll_l.top().first]=scroll_l.top().second;
scroll_l.pop();
}
while(!scroll_r.empty()){
rlen[scroll_r.top().first]=scroll_r.top().second;
scroll_r.pop();
}
for(int i=l;i<=r;++i){
llen[a[i]]=rlen[a[i]]=0;
}
}
void scroll_record(){
scroll_ans=res;
while(!scroll_l.empty()) scroll_l.pop();
while(!scroll_r.empty()) scroll_r.pop();
}
int ans[200001];
int main(){
freopen("ants.in","r",stdin);
freopen("ants.out","w",stdout);
// freopen("d.in","r",stdin);
// freopen("test.out","w",stdout);
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
}
for(int i=1;i<=m;++i){
scanf("%d %d",&q[i].l,&q[i].r);
q[i].id=i;
}
prework();
sort(q+1,q+m+1);
int l=0,r=0;
for(int i=1;i<=m;++i){
if(locate[q[i].l]!=locate[q[i-1].l]){
res=0;
for(int j=1;j<=n;++j){
llen[a[j]]=rlen[a[j]]=0;
}
r=rbor(q[i].l);
}
while(r<q[i].r) add(++r,false);
scroll_record();
l=min(q[i].r,rbor(q[i].l));
for(int j=q[i].l;j<=l;++j) add(j,true);
ans[q[i].id]=res;
scroll_back(q[i].l,l);
}
for(int i=1;i<=m;++i){
cout<<ans[i]<<"\n";
}
}
/*
8 3
3 1 7 2 5 8 6 4
1 4
5 8
1 7
*/