2021.7.13
2021.7.13
2021.7.13 模拟赛
Ⅱ
Ⅱ
Ⅱ
目录:
T1.消息传递
T2.JIH的玩偶
T3.摘取作物
T4.公路维护
T 1 : T1: T1:消息传递
分析:
洛谷有一道这题的弱
10
^{10}
10化版
l
i
n
k
link
link
这数据范围:
n
<
=
200000
n<=200000
n<=200000
先是树形
d
p
dp
dp
f
i
f_i
fi表示处理以
i
i
i为根的子树最少时间
从大到小排序后
f
i
f_i
fi可以直接求出
f
i
=
m
a
x
(
f
i
,
a
i
+
i
)
f_i=max(f_i,a_i+i)
fi=max(fi,ai+i) 把这个交到洛谷就过了
再考虑换根
d
p
dp
dp
g
i
g_i
gi表示除
i
i
i这棵子树后 其他部分以
i
i
i的父亲为根的整棵树的答案
g
i
g_i
gi已经求出 应该从通过
i
i
i求出每个儿子的
g
g
g
将所有的儿子和自己的
g
g
g值排序后 考虑删掉中间的一个
x
x
x后的答案
这个时候
x
x
x以前的数的
r
a
n
k
rank
rank不变
x
x
x后面的数的
r
a
n
k
rank
rank要
−
1
-1
−1
所以预处理一个前缀和后缀的 m a x max max 表示前 i i i个 或后 i i i个数中 f j + r a n k j f_j+rank_j fj+rankj的最小值 j j j的 g g g值就可以通过 m a x ( p r e x − 1 , s u f x + 1 − 1 ) max(pre_{x-1},suf_{x+1}-1) max(prex−1,sufx+1−1) 得出
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#pragma GCC optimize(2)
using namespace std;
const int N=2e5+5;
int n,m,x,head[N],tot,f[N],pre[N],suf[N];
int ans=N+1,query[N],g[N];
struct qaq{
int x,val;
}a[N];
struct node{
int to,next;
}edge[N<<1];
void add(int x,int y)
{
edge[++tot]=(node){y,head[x]};
head[x]=tot;
}
bool cmp(qaq x,qaq y){return x.val>y.val;}
inline void dp(int x,int fa)
{
for(int i=head[x];i;i=edge[i].next)
{
int qwq=edge[i].to;
if(qwq==fa) continue;
dp(qwq,x);
}
int cnt=0;
for(register int i=head[x];i;i=edge[i].next)
{
int qwq=edge[i].to;
if(fa==qwq) continue;
a[++cnt]=qaq{qwq,f[qwq]};
}
sort(a+1,a+cnt+1,cmp);
for(int i=1;i<=cnt;i++)
f[x]=max(f[x],a[i].val+i); //推出f
}
inline void work(int x,int fa)
{
int cnt=0;
if(x!=1) a[++cnt]=qaq{-1,g[x]}; //rank要-1
for(int i=head[x];i;i=edge[i].next)
{
int qwq=edge[i].to;
if(qwq==fa) continue;
a[++cnt]=qaq{qwq,f[qwq]};
}
sort(a+1,a+cnt+1,cmp);
pre[0]=suf[cnt+1]=0;
for(int i=1;i<=cnt;i++)
pre[i]=max(pre[i-1],a[i].val+i);
for(int i=cnt;i>=1;i--)
suf[i]=max(suf[i+1],a[i].val+i-1);
//处理前缀后缀
for(int i=1;i<=cnt;i++)
{
int qwq=a[i].x;
if(qwq>0) g[qwq]=max(pre[i-1],suf[i+1]); //推出g
}
if(ans>pre[cnt]) ans=pre[cnt],m=0;
if(ans==pre[cnt]) query[++m]=x;
for(int i=head[x];i;i=edge[i].next)
{
int qwq=edge[i].to;
if(qwq==fa) continue;
work(qwq,x);
}
}
int main(){
// freopen("news.in","r",stdin);
// freopen("news.out","w",stdout);
scanf("%d",&n);
for(register int i=2;i<=n;i++)
{
scanf("%d",&x);
add(x,i);add(i,x);
}
dp(1,0);
work(1,0);
sort(query+1,query+m+1);
printf("%d\n",ans+1);
for(int i=1;i<=m;i++)
printf("%d ",query[i]);
return 0;
}
T 2 : J I H T2:JIH T2:JIH的玩偶
分析:
因为只有从
x
x
x到
r
o
o
t
root
root 那就直接倍增
用类似
R
M
Q
RMQ
RMQ的倍增维护
f
a
t
h
e
r
,
m
a
x
,
m
i
n
father,max,min
father,max,min以及差
最后就倍增跳 每次取最大值和最小值的差 再更新父节点 就行了
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=2e5+5;
int fa[20][N],minn[20][N],maxn[20][N],k[20][N],n,T;
int main(){
// freopen("tree.in","r",stdin);
// freopen("tree.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&maxn[0][i]);
minn[0][i]=maxn[0][i];
}
for(int i=1,x,y;i<n;i++)
{
scanf("%d%d",&x,&y);
fa[0][x]=y;
}
for(int i=1;i<=18;i++)
for(int j=1;j<=n;j++)
{
int qwq=fa[i-1][j];
fa[i][j]=fa[i-1][qwq];
maxn[i][j]=max(maxn[i-1][j],maxn[i-1][qwq]); //最大值
minn[i][j]=min(minn[i-1][j],minn[i-1][qwq]); //最小值
k[i][j]=max(max(k[i][j],maxn[i-1][qwq]-minn[i-1][j]),max(k[i-1][qwq],k[i-1][j])); //差值
}
scanf("%d",&T);
while(T--)
{
int x,y,res=0x3f3f3f3f,ans=0;
scanf("%d%d",&x,&y);
for(int i=0;y;i++,y>>=1)
if(y&1)
{
ans=max(ans,max(k[i][x],maxn[i][x]-res)); //取最后差值
res=min(res,minn[i][x]);
x=fa[i][x]; //更新fa
}
printf("%d\n",ans);
}
return 0;
}
T 3 : T3: T3:摘取作物
分析:
网络流费用流
每个点向它的行列连边 源点向每行连 汇点向每列连
S
S
S到左边
n
n
n个点容量
2
2
2 费用
0
0
0
右边
m
m
m个点到
T
T
T 容量
2
2
2 费用
0
0
0
因为
中间
n
n
n到
m
m
m 容量为
1
1
1 费用为
w
i
,
j
w_{i,j}
wi,j
用
s
p
f
a
spfa
spfa判断流通 然后跑最大费用增广路 直到没有增广路
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=(35<<1);
int n,m,S,T,w[N][N],f[N][N],ans;
int vis[N],dis[N],pre[N],G[N][N],q[N*10];
int spfa()
{
memset(dis,138,sizeof(dis));
memset(vis,0,sizeof(vis));
int head=0,tail=1;
q[1]=S;dis[S]=0;
vis[S]=1;
while(head<=tail)
{
head++;
int qwq=q[head];
for(int i=1;i<=T;i++)
{
if(qwq^i&&f[qwq][i]&&dis[qwq]+G[qwq][i]>dis[i])
{
pre[i]=qwq;
dis[i]=dis[qwq]+G[qwq][i];
if(!vis[i]){
vis[i]=1;
q[++tail]=i;
}
}
vis[qwq]=0;
}
}
if(dis[T]<0) return 0;
int res=0x3f3f3f3f,qwq=T;
while(qwq^S)
{
res=min(res,f[pre[qwq]][qwq]);
qwq=pre[qwq];
}
qwq=T;
while(qwq^S)
{
f[pre[qwq]][qwq]-=res;
f[qwq][pre[qwq]]+=res;
qwq=pre[qwq];
}
ans+=dis[T];
return 1;
}
int main(){
// freopen("pick.in","r",stdin);
// freopen("pick.out","w",stdout);
scanf("%d%d",&n,&m);
S=n+m+1;T=n+m+2;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&w[i][j]);
f[i][j+n]=1; //中间n到m
G[i][j+n]=w[i][j]; //费用
G[j+n][i]=-w[i][j];
}
for(int i=1;i<=n;i++)
f[S][i]=2; //源点到左边
for(int i=n+1;i<=n+m;i++)
f[i][T]=2; //右边到汇点
while(spfa());
printf("%d",ans);
return 0;
}
T 4 : T4: T4:公路维护
分析:
显然数据结构了 那就线段树
维护两个
l
a
z
y
T
a
g
lazyTag
lazyTag 加值和最大值 加值的优先级更大
其余就是每种情况 的线段树操作了
操作 1 1 1要先看区间是不是能走的 再减 看减完有没有 < = 0 <=0 <=0的 就把这段区间标为 i n f inf inf
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e5+5;
int n,m,I,ans;
struct SegmentTree{
int lazyA,valA,lazyX,valX;
bool broken;
}a[N<<2],qwq;
void up(SegmentTree &a,SegmentTree &b,SegmentTree &c)
{
if(c.valA<b.valA) a.valA=c.valA,a.valX=c.valX;
else a.valA=b.valA,a.valX=b.valX;
a.broken=b.broken&c.broken;
}
void down(int x)
{
a[x<<1].lazyA=max(a[x<<1].lazyA+a[x].lazyX,a[x].lazyA);
a[x<<1|1].lazyA=max(a[x<<1|1].lazyA+a[x].lazyX,a[x].lazyA);
a[x<<1].valA=max(a[x<<1].valA+a[x].lazyX,a[x<<1].lazyA);
a[x<<1|1].valA=max(a[x<<1|1].valA+a[x].lazyX,a[x<<1|1].lazyA);
a[x<<1].lazyX+=a[x].lazyX;
a[x<<1|1].lazyX+=a[x].lazyX;
a[x].lazyA=-0x3f3f3f3f;
a[x].lazyX=0;
}
void build(int x,int l,int r)
{
if(l==r)
{
a[x].valA=I;
a[x].valX=l;
a[x].broken=1;
a[x].lazyA=-0x3f3f3f3f;
return;
}
int mid=(l+r)>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
up(a[x],a[x<<1],a[x<<1|1]);
}
bool check(int x,int l,int r,int L,int R) //看这段路能不能走
{
if(L==l&&r==R) return a[x].broken;
down(x);
int mid=(l+r)>>1;
bool res=1;
if(R<=mid) res=check(x<<1,l,mid,L,R);
else if(mid<L) res=check(x<<1|1,mid+1,r,L,R);
else res=check(x<<1,l,mid,L,mid)&check(x<<1|1,mid+1,r,mid+1,R);
up(a[x],a[x<<1],a[x<<1|1]);
return res;
}
void add(int x,int l,int r,int L,int R,int val) //加值
{
if(L==l&&r==R)
{
a[x].valA+=val;
a[x].lazyX+=val;
a[x].lazyA+=val;
return;
}
down(x);
int mid=(l+r)>>1;
if(R<=mid) add(x<<1,l,mid,L,R,val);
else if(mid<L) add(x<<1|1,mid+1,r,L,R,val);
else add(x<<1,l,mid,L,mid,val),add(x<<1|1,mid+1,r,mid+1,R,val);
up(a[x],a[x<<1],a[x<<1|1]);
}
void Repare(int x,int l,int r,int L,int R,int val) //修复这段路
{
if(L==l&&r==R)
{
a[x].lazyA=max(a[x].lazyA,val);
a[x].valA=max(a[x].valA,a[x].lazyA);
return;
}
down(x);
int mid=(l+r)>>1;
if(R<=mid) Repare(x<<1,l,mid,L,R,val);
else if(mid<L) Repare(x<<1|1,mid+1,r,L,R,val);
else Repare(x<<1,l,mid,L,mid,val),Repare(x<<1|1,mid+1,r,mid+1,R,val);
up(a[x],a[x<<1],a[x<<1|1]);
}
void UnRet(int x,int l,int r,int p) //这段路不可修复
{
if(l==r)
{
a[x].valA=0x3f3f3f3f;
a[x].broken=0;
return;
}
down(x);
int mid=(l+r)>>1;
if(p<=mid) UnRet(x<<1,l,mid,p);
else UnRet(x<<1|1,mid+1,r,p);
up(a[x],a[x<<1],a[x<<1|1]);
}
void found(int x,int l,int r,int L,int R) //看区间内有没有不能走的
{
if(L==l&&r==R)
{
if(!qwq.valX) qwq=a[x];
else if(a[x].valA<qwq.valA) qwq=a[x];
return;
}
down(x);
int mid=(l+r)>>1;
if(R<=mid) found(x<<1,l,mid,L,R);
else if(mid<L) found(x<<1|1,mid+1,r,L,R);
else found(x<<1,l,mid,L,mid),found(x<<1|1,mid+1,r,mid+1,R);
up(a[x],a[x<<1],a[x<<1|1]);
}
int main(){
// freopen("road.in","r",stdin);
// freopen("road.out","w",stdout);
scanf("%d%d%d",&n,&m,&I);
build(1,1,n);
int E=n;
while(m--)
{
int op,l,r,x;
scanf("%d%d%d%d",&op,&l,&r,&x);
if(op==1)
{
if(check(1,1,n,l,r))
{
ans++;
add(1,1,n,l,r,-x);
while(E)
{
qwq.valX=0;
found(1,1,n,l,r);
if(qwq.valA<=0&&qwq.lazyA<=0)
UnRet(1,1,n,qwq.valX);
else break;
E--;
}
}
continue;
}
if(op==2)
{
add(1,1,n,l,r,x);
continue;
}
if(op==3)
{
Repare(1,1,n,l,r,x);
continue;
}
}
printf("%d",ans);
return 0;
}