【upc】造船 | 思维、并查集

问题 E: 造船

时间限制: 1 Sec  内存限制: 128 MB
提交 状态

题目描述

小Y想要在虚拟世界里造船。最开始n个船的完成度全部都为0。小Y第iii时刻可以在ai和bi两艘船中选择一艘让这艘船的完成度+1。

由于国家政府是奇数控,所以所有偶数完成度的船只都将被摧毁,小Y想知道m时刻后能剩下来的船只最多有多少艘。

输入

第一行两个整数n和m

接下来m行,每行两个整数ai,bi

输出

一个整数,表示m时刻后能剩下来的船只最多有多少艘。

样例输入 Copy

8 6
1 2
3 4
4 5
6 7
7 8
8 6

样例输出 Copy

6

提示

20%的数据,m≤20,n≤100

对于100%的数据,1≤n,m≤200000

题目思路:

这个题目有点秀..

首先容易想到通过这些点的链接之后,某些点是具有关联性的:(想改变一个可以改变另一个)

所以用并查集找出其分的连通块,该联通块内是具有关联性的。

连通块外对当前块的答案无影响

之后我们把题目的操作看为,对边的操作。

接下来有一个技巧:a-b连边之后 ,如果选择a,就改变该边的方向 ,即a<-b

首先肯定的结论是:

1.如果边数>=点数可以直接将所有的点都+1,这点是肯定的(图联通)

之后考虑剩下的边 :

假设a - >b之间剩下三条边

假设c->d之间剩下四条边

假设e->f之间剩下五条边

那么此时因为首先将图中点都加了1(已经确保图中的点全为奇数),我们可以选择剩下的边每次对一个连接的点+2,那么最终的状态就是 每两个相邻点之间最多剩一条边

此时再怎么分配呢?

通过画图可以发现,这个边权的加法可以传递,也就是说:假设a->b->c,如果a,b间多一条边,那么就可以让b+1,然后b-c之间已经放好的边又可以让b-1,c+1,所以相当于c+1

但是这个过程需要连续:万一是a->b

所以我们可以发现所有的点不一定可以转移到同一个点上(因为这个图只有刚开始是一个环,注意上面黑字),有可能改变一次后就不在有环了,就会导致有的点的边权传递不过来。

但是!

此时可以发现,任意两个边可以传递到两边之间的任意一个点,使得中间的点+2

所以我们就可以这么考虑,把所有相邻的2个点都化成一团,这样就不会出现断开的情况(刚开始全部相连)

所以每相邻两点都可以组合,因此当剩下边是奇数时不能再组合。

所以这个集合的贡献即为 n-1或者n

2.如果当边数<点数,此时只有一种情况:树

可以发现树一定可以全部变奇数,贡献为点数

Code:

/*** keep hungry and calm CoolGuang!***/
#pragma GCC optimize(2)
#include 
#include
#include
#include
#include
#include
#define debug(x) cout<<#x<<":"< pp;
const ll INF=1e17;
const int maxn=2e5+6;
const int mod=998244353;
const double eps=1e-3;
inline bool read(ll &num)
{char in;bool IsN=false;
    in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;num=0;}else num=in-'0';while(in=getchar(),in>='0'&&in<='9'){num*=10,num+=in-'0';}if(IsN) num=-num;return true;}

ll n,m,p;
int sz[maxn],ed[maxn];
int pre[maxn];
int vis[maxn];
int Find(int x){
    return x == pre[x]?x:pre[x] = Find(pre[x]);
}
int main(){
    read(n);read(m);
    for(int i=1;i<=n;i++) pre[i] = i,sz[i] = 1;
    for(int i=1;i<=m;i++){
        int x,y;scanf("%d%d",&x,&y);
        int dx = Find(x),dy = Find(y);
        if(dx != dy){
            pre[dx] = dy;
            sz[dy] += sz[dx];
            ed[dy] += ed[dx]+1;
        }
        if(dx == dy) ed[dx]++;
    }
    ll ans = 0;
    for(int i=1;i<=n;i++){
        int dx = Find(i);
        if(!vis[dx]){
            if(ed[dx]-sz[dx]<0) ans += sz[dx]-1;
            else if((ed[dx]-sz[dx])&1) ans += sz[dx]-1;
            else ans += sz[dx];
            vis[dx] = 1;
        }
    }
    printf("%lld\n",ans);
    return 0;
}
/**
10 10
1 2
3 4
5 6
7 8
7 8
7 8
7 8
7 8
7 8
7 8
**/

 

你可能感兴趣的:(upc经典题目及题解整理,并查集,思维锻炼)