码迷,mamicode.com
首页 > 移动开发 > 详细

4.18 省选模拟赛 桥 边双联通分量 长链剖分维护贪心

时间:2020-04-24 21:57:09      阅读:81      评论:0      收藏:0      [点我收藏+]

标签:open   ret   add   缩点   alt   end   com   最优   自己   

技术图片
技术图片

只存在加边操作 所以每次只对割边有影响。

考虑求出所有的边双联通分量 然后进行缩点。

那么原图就变成了一颗树 且所有边都是割边。

考虑k==1的时候 显然是求出树的直径。

考虑k>1时 一个错误的贪心:把刚才树的直径上的边标记为0 然后再求直径......

容易构造出反例让其错误。

题解上的做法过于神仙 看不懂。

给一种自己做CF某道题后得到的贪心思路。

容易发现选出k条边 某种意义上来说是选出了2k个点。

我们还可以知道这2k个点中 其中必然有两个点包含树的直径两端点。

但是直接选出2k个点可能是不合法的。而且此时无根很难计算出来答案。

我们知道了必然有两个点是树的直径两端点了 以其中一个点为根。

那么剩下的就是选取2k-1个点每个点的最初权值为点到根路径上的边数。

考虑每次选取最大的。这个贪心容易使用长链剖分进行优化 排一下序或者使用堆维护即可。

正确性:最初根为直径某个端点显然正确,接着这2k个点包括根显然是可以两两配对组成k条边和题目吻合。

最优性:每次权值都尽可能的大所以是最优的。(还需要斟酌一下 好多题都是这样贪的

const int MAXN=200010;
int n,m,Q,id,len=1,cnt,cc,len1=1,s1,s2,maxx;
int c[MAXN],vis[MAXN],dis[MAXN];
int dfn[MAXN],low[MAXN],mark[MAXN<<1],f[MAXN],q[MAXN],mx[MAXN];
int lin[MAXN],ver[MAXN<<1],nex[MAXN<<1],sz[MAXN],son[MAXN],fa1[MAXN];
int lin1[MAXN],ver1[MAXN<<1],nex1[MAXN<<1],e1[MAXN<<1];
inline void add(int x,int y)
{
	ver[++len]=y;
	nex[len]=lin[x];
	lin[x]=len;
}
inline void add1(int x,int y,int z)
{
	ver1[++len1]=y;
	nex1[len1]=lin1[x];
	lin1[x]=len1;
	e1[len1]=z;
}
inline void dfs(int x,int las)
{
	dfn[x]=low[x]=++cnt;
	go(x)
	{
		if(i==(las^1))continue;
		if(!dfn[tn])
		{
			dfs(tn,i);
			low[x]=min(low[x],low[tn]);
			if(low[tn]>dfn[x])mark[i]=1;
		}
		else low[x]=min(low[x],dfn[tn]);
	}
}
inline void dfs(int x)
{
	vis[x]=1;c[x]=id;
	go(x)
	{
		if(vis[tn]||mark[i]||mark[i^1])continue;
		dfs(tn);
	}
}
inline int bfs(int w)
{
	maxx=0;++cc;
	int l=0,r=0,ww=0;
	q[++r]=w;dis[w]=0;vis[w]=cc;
	while(++l<=r)
	{
		int x=q[l];
		if(dis[x]>maxx)
		{
			ww=x;
			maxx=dis[x];
		}
		for(int i=lin1[x];i;i=nex1[i])
		{
			int tn=ver1[i];
			if(vis[tn]!=cc)
			{
				dis[tn]=dis[x]+e1[i];
				vis[tn]=cc;
				q[++r]=tn;
			}
		}
	}
	return ww;
}
inline int dp(int x,int fa)
{
	if(x==s2)return 1;
	for(int i=lin1[x];i;i=nex1[i])
	{
		int tn=ver1[i];
		if(tn==fa)continue;
		if(dp(tn,x))
		{
			e1[i]=e1[i^1]=0;
			return 1;
		}
	}
	return 0;
}
inline int getfather(int x){return x==f[x]?x:f[x]=getfather(f[x]);}
inline void dp1(int x,int fa)
{
	sz[x]=sz[fa]+1;mx[x]=sz[x];
	fa1[x]=fa;
	for(int i=lin1[x];i;i=nex1[i])
	{
		int tn=ver1[i];
		if(tn==fa)continue;
		dp1(tn,x);
		//cout<<mx[tn]<<‘ ‘<<mx[x]<<endl;
		if(mx[tn]>mx[x])
		{
			son[x]=tn;
			mx[x]=mx[tn];
		}
	//	cout<<son[x]<<endl;
	}
}
priority_queue<int>s;
inline void dp2(int x,int fa)
{
	if(fa==x)
	{
		if(x==s1)s.push(mx[x]-sz[x]);
		else s.push(mx[x]-sz[fa1[x]]);
		//cout<<mx[x]-sz[x]<<endl;
		//cout<<son[x]<<‘ ‘<<x<<endl;
	}
	if(son[x])dp2(son[x],fa);
	for(int i=lin1[x];i;i=nex1[i])
	{
		int tn=ver1[i];
		if(tn==fa1[x]||son[x]==tn)continue;
		dp2(tn,tn);
		//cout<<"ww"<<endl;
	}
}
int main()
{
	//freopen("1.in","r",stdin);
	freopen("bridge.in","r",stdin);
	freopen("bridge.out","w",stdout);
	get(n);get(m);get(Q);
	rep(1,m,i)
	{
		int get(x);int get(y);
		add(x,y);add(y,x);
	}
	dfs(1,0);cc=1;
	rep(1,n,i)if(!vis[i])++id,dfs(i);
	rep(1,id,i)f[i]=i;
	rep(1,n,j)go(j)
	{
		if(c[j]==c[tn])continue;
		int xx=getfather(c[j]);
		int yy=getfather(c[tn]);
		if(xx==yy)continue;
		f[xx]=yy;
		add1(c[j],c[tn],1);
		add1(c[tn],c[j],1);
	}
	--id;int ans=0;
	s1=bfs(1);s2=bfs(s1);
	dp1(s1,0);
	dp2(s1,s1);
	ans+=s.top();
	s.pop();
	put(ans);
	rep(2,Q,i)
	{
		if(s.size())
		{
			ans+=s.top();
			s.pop();
		} 
		if(s.size())
		{
			ans+=s.top();
			s.pop();
		} 
		put(ans);
	}
	return 0;
}

4.18 省选模拟赛 桥 边双联通分量 长链剖分维护贪心

标签:open   ret   add   缩点   alt   end   com   最优   自己   

原文地址:https://www.cnblogs.com/chdy/p/12770030.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!