20210311互测

T2(CF571D Campus)

题目链接

solution

假设对于每个询问,我们可以求出当前位置最近一次被清零的时间\(t\) ,那么答案就是只考虑第一类集合操作时当前的答案减去\(t\) 时刻的答案。

于是可以将操作和询问离线下来,先处理出每个询问位置上次被清零的时间,然后再顺次考虑第一类集合的操作,用带权并查集计算出当前时刻某个位置的值。

问题转化为如何求出每个位置上次被清零的时间。

这个其实也可以用带权并查集来做。清零时可以直接在根上打标记,那么每个节点最近一次被清零的时刻就是从节点到根的路径上所有点权的最大值。合并的时候需要特殊注意,需要建立虚点。

time complexity

\(\mathcal O(n\log_2n)\)

code

#include<bits/stdc++.h>
#define mp make_pair
using namespace std;
const int N=1e6+5;
typedef long long ll;
typedef pair<int,int> pii;
int n,m;
int opt[N],p[N],q[N],fa[N],d[N],sz[N];ll w[N],ans[N];
vector<pii>qry1[N],qry2[N];
inline int fd(int x){while(x!=fa[x])x=fa[x];return x;}
int _fd(int x)
{
	if(x==fa[x])return x;
	int p=_fd(fa[x]);w[x]=max(w[x],w[fa[x]]);
	return fa[x]=p;
}
inline ll q2(int x)
{
	ll ret=0;
	for(;;x=fa[x])
	{
		ret+=w[x];
		if(x==fa[x])break;
	}
	return ret;
}
int s[N],t[N];
int main()
{
	scanf("%d%d",&n,&m);char c[5];
	for(int i=1;i<=m;++i)
	{
		scanf("%s",c);
		if(c[0]=='U')opt[i]=1,scanf("%d%d",p+i,q+i);
		else if(c[0]=='M')opt[i]=2,scanf("%d%d",p+i,q+i);
		else if(c[0]=='A')opt[i]=3,scanf("%d",p+i);
		else if(c[0]=='Z')opt[i]=4,scanf("%d",p+i);
		else opt[i]=5,scanf("%d",p+i);
	}
	for(int i=1;i<=n;++i)fa[i]=i,w[i]=0;int tot=n;
	for(int i=1;i<=m;++i)
	{
		if(opt[i]==2)
		{
			int f1=_fd(p[i]),f2=_fd(q[i]);
			if(f1==f2)continue;
			++tot;w[tot]=0;fa[tot]=tot;
			fa[f1]=fa[f2]=tot;
		}
		else if(opt[i]==4)
		{
			int f=_fd(p[i]);
			w[f]=1ll*i;
		}
		else if(opt[i]==5)
		{ 
			_fd(p[i]);s[i]=max(w[p[i]],w[fa[p[i]]]),t[i]=i;
			qry1[s[i]].push_back(mp(p[i],i));
			qry2[t[i]].push_back(mp(p[i],i));
		}
	}
	for(int i=1;i<=n;++i)fa[i]=i,d[i]=1,w[i]=0,sz[i]=1;
	for(int i=1;i<=m;++i)
	{
		if(opt[i]==1)
		{
			int f1=fd(p[i]),f2=fd(q[i]);
			if(f1==f2)continue;
			if(d[f1]<d[f2])swap(f1,f2);
			fa[f2]=f1,w[f2]-=w[f1],sz[f1]+=sz[f2];
			d[f1]=max(d[f1],d[f2]+1);
		}
		else if(opt[i]==3)
		{
			int f=fd(p[i]);
			w[f]+=1ll*sz[f];
		}
		for(pii u:qry1[i])
			ans[u.second]-=q2(u.first);
		for(pii u:qry2[i])
			ans[u.second]+=q2(u.first);
	}
	for(int i=1;i<=m;++i)
		if(opt[i]==5)printf("%lld\n",ans[i]);
	return 0;
}

T3(SPOJ COT6)

description

大小为\(n\) 的树,以\(1\) 为根,顶点带权。定义一条直链为一条祖先到子孙的链,可以只有一个点,权值为其上所有点的权值和。现在要求把点集剖分成若干直链,使得所有直链权值平方和最小。

data range

\(n\le 10^6\)

solution

毫无头绪??好的来\(dp\) 吧。。

\(f_u\) 表示只考虑以\(u\) 为根的子树时所有直链权值平方和的最小值。

考虑转移。不妨记\(x\)\(u\) 所在直链的底端,那么有

\[f_u=min_x((s_x-s_{fa_u})^2+g_{u,x}) \]

其中\(s_x\) 表示从根到\(x\) 的路径上的点权和,\(g_{u,x}\) 表示当\(x\)\(u\) 所在直链的底端时其他子树的\(f\) 之和。

直接做肯定是\(\mathcal O(n^2)\) 的,考虑优化。

不妨令

\[k=(s_x-s_{fa_u})^2+g_{u,x} \]

现在需要找到合适的\(x\) 使得\(k\) 最小。

化成一次函数的形式:

\[s_x^2+g_{u,x}=2s_{fa_u}s_x+k-s_{fa_u}^2 \]

那么相当于对于每个节点\(x\) ,它在平面上对应的点为\((s_x,s_x^2+g_{u,x})\) 。现在需要找到一条经过当前平面上某个点且斜率为\(2s_{fa_u}\) 直线同时最小化它的截距。

不难想到维护一个下凸壳,那么每次转移就相当于在凸包上二分。

现在考虑对于\(u\) 如何合并它所有子树的凸包。

这里再令\(t_u\) 表示\(u\) 的所有子节点\(v\)\(f_v\) 之和。

首先我们会有

\[g_{u,x}=g_{v,x}+t_u-f_v \]

因此相当于子树\(v\) 对应的凸包会向上移动\(t_u-f_v\) 的距离,这个可以通过打标记维护一个相对位置的凸包来解决。

之后的话其实就是启发式合并,将较小凸包中的点一个一个拿出来,加上打在它上面的标记,然后插入到大凸包中。

单点插入的话就是左边弹一弹,右边弹一弹,最后再判一下当前的点是不是凸包上的点即可。

具体实现时用\(set\) 会十分方便。

time complexity

\(\mathcal O(n\log_2^2n)\)

code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;const ll inf=1e18;
int n,fa[N];ll z[N],f[N],tag[N];
int tot,fi[N],ne[N],to[N];
struct pt{ll x,y;};
inline pt operator-(const pt&x,const pt&y){return {x.x-y.x,x.y-y.y};}
inline ll cross(const pt&x,const pt&y){return x.x*y.y-x.y*y.x;}
inline bool issuk(const pt&x,const pt&y,const pt&z){return cross(y-z,x-z)>=0ll;}
inline bool operator<(const pt&x,const pt&y){return x.x==y.x?x.y<y.y:x.x<y.x;}
set<pt>s[N];
inline ll sqr(ll x){return x*x;}
inline void add(int x,int y)
{
	ne[++tot]=fi[x],fi[x]=tot,to[tot]=y;
}
inline void ins(int u,pt now)
{
	now.y-=tag[u];
	auto&a=s[u];
	auto p=a.lower_bound(now),q=p;
	if(p!=a.end()&&(*p).x==now.x)
	{
		if(now.y>=(*p).y)return;
		a.erase(p);
	}
	while(!a.empty())
	{
		p=a.lower_bound(now);
		if(p==a.begin())break;
		p--;if(p==a.begin())break;
		q=p;p--;
		if(issuk(*p,*q,now))a.erase(q);
		else break;
	}
	while(!a.empty())
	{
		p=a.lower_bound(now);
		if(p==a.end())break;
		q=p;p++;if(p==a.end())break;
		if(issuk(now,*q,*p))a.erase(q);
		else break;
	}
	p=a.lower_bound(now);
	if(p==a.begin()||p==a.end())
		return a.insert(now),void();
	q=p;p--;
	if(!issuk(*p,now,*q))a.insert(now);
}
inline double slope(const pt&x){return 1.0*x.y/x.x;}
inline ll work(int u)
{
	auto&a=s[u];
	if(a.empty())return inf;
	pt ans;ll k=2*z[fa[u]];
	auto p=a.begin(),q=p;p++;
	if(a.size()==1)ans=*q;
	else if(slope(*p-*q)>=k)ans=*q;
	else
	{
		ll l=(*a.begin()).x+1,r=(*a.rbegin()).x+1;
		while(l+1<r)
		{
			ll mid=l+r>>1;
			p=a.lower_bound({mid,-inf});
			q=p;p--;
			slope(*q-*p)<k?l=mid:r=mid;
		}
		ans=*a.lower_bound({l,-inf});
	}
	ans.y+=tag[u];
	return ans.y+sqr(z[fa[u]])-2*z[fa[u]]*ans.x;
}
void dfs(int u)
{
	z[u]+=z[fa[u]];
	ll sum=0;int son=0;s[son].clear();
	for(int i=fi[u];i;i=ne[i])
	{
		int v=to[i];dfs(v);
		sum+=f[v];
		if(s[son].size()<s[v].size())son=v;
	}
	swap(s[u],s[son]);tag[u]=tag[son]+sum-f[son];
	for(int i=fi[u];i;i=ne[i])
	{
		int v=to[i];
		if(v==son)continue;
		tag[v]+=sum-f[v];
		for(pt t:s[v])
			t.y+=tag[v],ins(u,t);
	}
	f[u]=min(work(u),sqr(z[u]-z[fa[u]])+sum);
	ins(u,{z[u],sqr(z[u])+sum});
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)scanf("%lld",&z[i]);
	for(int i=2;i<=n;++i)
		scanf("%d",fa+i),add(fa[i],i);
	dfs(1);printf("%lld\n",f[1]);
	return 0;
}

posted @ 2021-03-14 22:00  BILL666  阅读(94)  评论(0编辑  收藏  举报