【联合选讲】「LibreOJ β Round #3」绯色 IOI(悬念)

题目

Description
胖头鱼从鱼戏团逃脱后,被主人一路追捕,他慌不择路地跑进了一颗n个节点的池子树,池子树的所有度数为1的点就是出口。
假如他现在在节点i,那么每个时刻他能选择向某个与当前点有边的点游过去。初始时刻主人可能召唤若干丂风出现在若干出口,他们的移动规则与胖头鱼相同。
胖头鱼会被抓到,有两种情况:
(1)某时刻结束时,他与丂风在同一个点。(甚至这个点是出口)
(2)某时刻进行时,与丂风经过同一条边。

当胖头鱼成功躲开丂风并走到出口时,他就自由了。
现在胖头鱼想知道,当他初始在1…n中每个点时,主人需要多少丂风才能抓到他。

为了让你理解题意,以下信息可能是有用的:
(1) 当胖头鱼选择了某个出口作为起点时,主人只需要一个丂风放在那就可以抓到他了。
(2) 很显然,人数上界就是出口数量。

Input
第一行一个整数n表示点数,接下来n-1行每行两个数表示一条边。

Output
输出n个整数,每一行表示当胖头鱼初始在第i个点时,主人需要派出多少丂风

Sample Input
7
1 2
1 3
3 4
3 5
4 6
5 7

Sample Output
3
1
3
3
3
1
1

Data Constraint
数据:n<=7e4
实际能做:n<=1e6

思路

怎么判断左边满配?
Hall定理
在这里插入图片描述
现在一个匹配相当于给所有边定向,要求每个点入度不超过1,的最大权值和。
对于一个基环树,树上的边的方向是确定的,环上只有两种方向。

对于一个树,一种定向方法相当于选择一个根,做外向树。
先随便选择一个点作根,讨论一条边的贡献,发现要么是对子树外的点作根有贡献,要么是对子树内的点作根有贡献。
用线段树维护dfs序上的区间加、区间最大值即可。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=500077;
int n,m,T,Q,tot,tim,top,ccnt,tcnt,ans;
int a[N],b[N],h[N],ne[N<<1],to[N<<1],w[N<<1],rev[N<<1];
int vis[N],dsu[N],tag[N],typ[N<<1];
int in[N],out[N],fa[N],tr[N<<2],lz[N<<2],rt[N];
int st[N],id[N<<1],Lv[N],Rv[N];

void add(int x,int y,int z) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,w[tot]=z;}
int getdsu(int x) {return x==dsu[x]?x:dsu[x]=getdsu(dsu[x]);}

void pd(int i) 
{
	int l=i<<1,r=(i<<1)|1;
	tr[l]+=lz[i],tr[r]+=lz[i],lz[l]+=lz[i],lz[r]+=lz[i],lz[i]=0;
}
void ins(int l,int r,int s,int t,int i,int v) 
{
	if(l<=s&&t<=r) {lz[i]+=v,tr[i]+=v;return;}
	int mid=(s+t)>>1; if(lz[i]) pd(i);
	if(l<=mid) ins(l,r,s,mid,i<<1,v);
	if(mid+1<=r) ins(l,r,mid+1,t,(i<<1)|1,v);
	tr[i]=max(tr[i<<1],tr[(i<<1)|1]);
}
int query(int l,int r,int s,int t,int i) 
{
	if(l<=s&&t<=r) return tr[i];
	int mid=(s+t)>>1,re=0; if(lz[i]) pd(i);
	if(l<=mid) re=query(l,r,s,mid,i<<1);
	if(mid+1<=r) re=max(re,query(l,r,mid+1,t,(i<<1)|1));
	return re;
}
void work_tree(int t,int v) 
{
	int x=to[t],y=to[rev[t]];
	if(x==fa[y]) ins(in[y],out[y],1,tim,1,v);
	else ins(in[id[t]],out[id[t]],1,tim,1,v),ins(in[x],out[x],1,tim,1,-v);
}

void dfs_tree(int x,int rt,int las) 
{
	in[x]=++tim,fa[x]=las,vis[x]=1;
	for(int i=h[x]; i; i=ne[i])
		if(to[i]!=las)
			dfs_tree(to[i],rt,x),id[i]=id[rev[i]]=rt,typ[i]=typ[rev[i]]=1;
	out[x]=tim;
}
void ring(int x,int y,int no,int las) 
{
	fa[x]=las,vis[x]=1;
	if(x==y)
	{
		for(int i=1; i<=top; i++)
		{
			typ[st[i]]=2,Lv[ccnt]+=w[st[i]];
			typ[rev[st[i]]]=3,Rv[ccnt]+=w[rev[st[i]]];
		}
	}
	for(int i=h[x]; i; i=ne[i]) if(to[i]!=las&&i!=no&&i!=rev[no])
	{
		id[i]=id[rev[i]]=ccnt,typ[i]=4;
		st[++top]=i,ring(to[i],y,no,x),--top;
	}
}
void prework() 
{
	for(int i=1; i<=m; i++) 
	{
		if(vis[i]) continue;
		int k=getdsu(i);
		if(!tag[k]) dfs_tree(i,i,0),rt[++tcnt]=i;
		else 
		{
			++ccnt,ring(to[tag[k]],to[rev[tag[k]]],tag[k],0);
			id[tag[k]]=id[rev[tag[k]]]=ccnt;
			typ[tag[k]]=2,Lv[ccnt]+=w[tag[k]];
			if(tag[k]!=rev[tag[k]]) typ[rev[tag[k]]]=3,Rv[ccnt]+=w[rev[tag[k]]];
			ans+=max(Lv[ccnt],Rv[ccnt]);
		}
	}
	for(int i=1; i<=tot; i++)
		if(typ[i]==4) ans+=w[i];
		else if(typ[i]==1) work_tree(i,w[i]);
	for(int i=1; i<=tcnt; i++) ans+=query(in[rt[i]],out[rt[i]],1,tim,1);
}

void work() 
{
	Q=read(),printf("%d\n",ans);
	while(Q--) 
	{
		int x=read()-ans*T,y=read()-ans*T;
		if(typ[x]==0);
		else if(typ[x]==1) 
		{
			int kv=query(in[id[x]],out[id[x]],1,tim,1);
			work_tree(x,y-w[x]),w[x]=y;
			ans+=query(in[id[x]],out[id[x]],1,tim,1)-kv;
		}
		else if(typ[x]==2) 
		{
			int kv=max(Lv[id[x]],Rv[id[x]]);
			Lv[id[x]]+=y-w[x],w[x]=y;
			ans+=max(Lv[id[x]],Rv[id[x]])-kv;
		}
		else if(typ[x]==3) 
		{
			int kv=max(Lv[id[x]],Rv[id[x]]);
			Rv[id[x]]+=y-w[x],w[x]=y;
			ans+=max(Lv[id[x]],Rv[id[x]])-kv;
		}
		else if(typ[x]==4) ans+=y-w[x],w[x]=y;
		printf("%d\n",ans);
	}
}

int main()
{
	n=read(),m=read(),T=read();
	for(int i=0; i<n; i++) a[i]=read();
	for(int i=0; i<n; i++) b[i]=read();
	for(int i=1; i<=m; i++) dsu[i]=i;
	for(int i=0; i<n; i++) 
	{
		int x=(a[i]+b[i])%m+1,y=(a[i]-b[i]+m)%m+1;
		if(x>y) swap(x,y);
		add(y,x,read());
		int r1=getdsu(x),r2=getdsu(y);
		if(r1==r2) tag[r1]=tot;
		else dsu[r1]=r2,tag[r2]|=tag[r1];
		if(x!=y) rev[tot]=tot+1,rev[tot+1]=tot,add(x,y,read());
		else rev[tot]=tot;
	}
	prework(),work();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值