NOI.AC 712 练级

题目大意

%   对于 n n n 个数,初始值为零,共有 q q q 组操作,形如 ( u , v ) ( u,v) (u,v),你需要在 u u u v v v 种选择一个数,将以这个数为下标的位置加一,使得最终奇数个数尽量多,输出最多的个数。

题解

%   本题仅仅要求奇数尽量多,因而两次操作相当于没有操作,这让我们想到了异或操作。更进一步,如果我们对于每个无序二元组 ( u , v ) (u,v) (u,v),先钦定 u u u 作为加一的对象,那么如果后面我们需要反悔,只需要将 u u u v v v 的权值均异或上一即可。
  这样考虑似乎很难找到多个二元组之间的关系,我们考虑将这些二元关系建成一张图, ∀   ( u , v ) \forall \ (u,v)  (u,v),我们建立一张图 G = { V , E } G=\{V,E\} G={V,E} ( u , v ) ∈ E (u,v)\in E (u,v)E1,点权为这个数是否为奇数。
  我们仍然按照之前的说法,对于一条边,钦定一个端点异或上1,可以发现,对于一个连通块,其中任意两个权值为零的点均可以通过给某些边的两个端点异或上1的方法,将这两个权值为零的点的权值均变为1。
  于是我们得到了一个完整的算法,先建图,钦定任意一个端点,将其点权异或一,然后计算出每个连通块中点权为1的点数 a a a,点权为零的点数 b b b,则该连通块最多能有 ⌊ a + b 2 ⌋ \lfloor\frac {a+b}2\rfloor 2a+b 个点的点权为1,也就是答案。

代码

%   代码非常好打。

#include<bits/stdc++.h>
using namespace std;
#define maxn 200010
struct edge{
	int v,next;
}edges[maxn<<1];
int head[maxn],cnte;
void ins(int u,int v){
	edges[++cnte]=(edge){v,head[u]};
	head[u]=cnte;
}
int d[maxn],vis[maxn];
void dfs(int u,int fa,int &c0,int &c1){
	if(vis[u]) return;
	vis[u]=true;
	c1+=d[u];c0+=!d[u];
	for(int i=head[u];i;i=edges[i].next){
		int v=edges[i].v;
		if(v==fa) continue;
		dfs(v,u,c0,c1);
	}
}
int n,m;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;i++){
		scanf("%d%d",&u,&v);d[u]^=1; 
		if(u!=v) ins(u,v),ins(v,u);
	} int ans=0;
	for(int i=1;i<=n;i++){
		if(!vis[i]){
			int c0=0,c1=0;
			dfs(i,-1,c0,c1);
			if(c0&1) ans+=(c0-1)+c1;
			else ans+=c0+c1;
		}
	} printf("%d\n",ans);
	return 0;
}

  1. 建立一张图,对于每个二元组 ( u , v ) (u,v) (u,v),在图中连边 ( u , v ) (u,v) (u,v)↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值