zr23noip20连day2 C解题报告

题目描述:

给你一个 n n n 个点 m m m 条边的简单无向连通图,点按照 0 ∼ n − 1 0∼n−1 0n1 编号,现在你需要删掉若干条边,最大化度数为奇数的点的个数。
当然了,你还需要给出构造,即输出一个长度为 m m m 01 01 01 串, 1 1 1 表示保留这个边, 0 0 0 表示删掉这个边,请输出字典序最大的方案。

输入格式:

第一行包含两个整数 n , m n,m n,m ,表示简单无向连通图中的点数和边数。
接下来 m m m 行,第 i i i 行包含两个整数 x i , y i xi,yi xi,yi ,表示第 i i i 条边连接编号 x i xi xi 和编号 y i yi yi 的点。

输出格式:

输出一个长度为 m m m 且由 0 0 0 1 1 1 组成的字符串,若第 i i i 个字符是 0 0 0,代表奇数度数的点最多的时候第 i i i 条边被删掉了。若是 1 1 1 ,则没被删掉。

思路分析:

若有多种删边的方案使得奇数度数的点最多,则输出使答案的字典序最大的方案。

考虑这个构造只需要一颗生成树即可完成。如果想使字典序最大,那么显然以边的编号为关键字做最大生成树。非树边在答案中的状态就全部赋为1。这样做到了初步保证字典序最大。

考虑每次随机选两个偶数度的点,将他们之间的一条路径的状态取反,我们发现这样的操作仅改变这两个点度数的奇偶性。那么我们可以对度数为偶数的点两两配对,容易得出,最后要么所有点的度数都是奇数,要么只有一个点的度数是偶数。问题在于如何最大化答案字典序。

可以发现在树的形态固定的条件下,无论按何种方式配对,最终的结果都是一样的。

这时如果所有点的度数都是奇数,那么只需要求出所有边的最终状态就做完了。

如果有一个点的度数是偶数,则还能改变从这个点开始到任意一个点路径上边的状态。这里我的做法是以该点为根dfs求出每条边到根路径上第一个编号小于它的边并在两者间连边,从而得到一棵新的树,然后在新的树上贪心。

贪心有很多方法,笔者使用的是以唯一的偶数点为根跑dfs,找到最近的编号小于它的点并进行取反操作即可。

Code:

#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define mpi make_pair
#define fir first
#define sec second

const int NUM=6e5+5;
const int NUMM=9e5+5;

struct node
{
  int x,y;
}a[NUMM];

int n,m,top,top1;
int f[NUM],h[NUM],ans[NUM*2],deg[NUM],idx[NUM],fa[NUM],sta[NUM],sta1[NUM],dep[NUM];
vector<pii> e[NUM];
vector<int> vec,ee[NUMM];

inline void dfs(int x,int ffa)
{
	int id=0;
	dep[x]=dep[ffa]+1;
  for(auto y:e[x])
  {
    if(y.fir!=ffa)
    {
      dfs(y.fir,x);
      h[x]^=h[y.first];
    }
    else
      id=y.sec;
  }
	if(h[x])
    ans[id]^=1;
	fa[x]=ffa;
  idx[x]=id;
}

inline int findfa(int x)
{
  if(f[x]==x)
    return x;
  return f[x]=findfa(f[x]);
}

inline void slove(int x,int ffa)
{
	int id=idx[x],temppp=top1;
	if(id)
	{
		while(top && sta[top]>id)
      sta1[++top1]=sta[top--];
		ee[sta[top]].emplace_back(id);
		sta[++top]=id;
	}
  for(auto y:e[x])
  {
    if(y.fir!=ffa)
      slove(y.fir,x);
  }
	if(id)
    --top;
	while(top1>temppp)
    sta[++top]=sta1[top1--];
}

inline void dfs1(int x)
{
	int minn=m+1;
  for(auto y:ee[x])
  {
    if(!ans[y])
      minn=min(minn,y);
  }
	if(x==0 && minn==m+1)
    return;
	if(minn==m+1)
	{
		int xx=a[x].x,yy=a[x].y;
		if(dep[xx]<dep[yy])
      xx=yy;
    while(idx[xx])
    {
      ans[idx[xx]]^=1;
      xx=fa[xx];
    }
	}
	else
    dfs1(minn);
}

signed main()
{
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);

	cin>>n>>m;
	for(int i=1;i<=m;++i)
  {
    cin>>a[i].x>>a[i].y;
    ++a[i].x;
    ++a[i].y;
    ans[i]=1;
    deg[a[i].x]^=1;
    deg[a[i].y]^=1;
  }
	for(int i=1;i<=n;++i)
    f[i]=i;
	for(int i=m;i;--i)
	{
		int x=findfa(a[i].x),y=findfa(a[i].y);
		if(x==y)
      continue;
		f[x]=y;
    e[a[i].x].emplace_back(mpi(a[i].y,i));
    e[a[i].y].emplace_back(mpi(a[i].x,i));
	}
	for(int i=1;i<=n;++i)
  {
    if(!deg[i])
      vec.emplace_back(i);
  }
  for(int i=1;i<vec.size();i+=2)
  {
    h[vec[i]]^=1;
    h[vec[i-1]]^=1;
  }
  if(vec.size()%2==0)
    dfs(1,0);
	else
	{
		int rt=vec.back();
		dfs(rt,0);
		ee[0].clear();
		slove(rt,0);
		dfs1(0);
	}
	for(int i=1;i<=m;++i)
    cout<<ans[i];

	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值