CF1423N

发布时间 2023-03-22 21:15:36作者: CelticOIer

先将无向图转为一个 DAG,直接从大点向小点连边就可以。

考虑增量构造,假设当前已经构造完了集合 \({1,2,\cdots,i-1}\),他们满足有边的点权值不同。现在我们加入第 \(i\) 个点。那么枚举他的出边,将出点按点权分为两类。如果我们最初令所有边权都为 \(1\),那么当前点的出边边权也都为 \(1\),所以出点点权为 \(0\) 可以通过将点权 \(+1\),边权 \(-1\) 的方式使得 \(i\) 号点的点权减 \(1\),同时这个点原本的权值不变。同理点权为 \(0\) 的出点可以使 \(i\) 号点的点权 \(+1\) 。于是可以得到 \(deg_i+1\) 种不同的点权,根据抽屉原理可得一定有一个点权和出点都不同。

复杂度 \(O(n+k)\)

#include<bits/stdc++.h>
#define N 2001001
#define MAX 5005
#define inf 1e18
using namespace std;
typedef long long ll;
typedef long double db;
inline void read(ll &ret)
{
    ret=0;char c=getchar();bool pd=false;
    while(!isdigit(c)){pd|=c=='-';c=getchar();}
    while(isdigit(c)){ret=(ret<<1)+(ret<<3)+(c&15);c=getchar();}
    ret=pd?-ret:ret;
    return;
}
ll n,k,x,y,val[N],cv[N],ce[N],fr[N],ed[N];
bool vis[N];
vector<pair<ll,ll> >v[N];
signed main()
{
	read(n);
	read(k);
	for(int i=1;i<=k;i++)
	{
		read(x);
		read(y);
		fr[i]=x,ed[i]=y;
		val[x]++,val[y]++;
		ce[i]=1;
		if(x<y)
			v[y].push_back(make_pair(x,i));
		else
			v[x].push_back(make_pair(y,i));
	}
	for(int i=1;i<=n;i++)
	{
		ll cnt0=0,cnt1=0;
		for(auto j:v[i])
		{
			ll x=j.first;
			if(cv[x]==0)
				cnt0++;
			else
				cnt1++;
			vis[val[x]]=true;
		}
		for(int j=val[i]-cnt0;j<=val[i]+cnt1;j++)
		{
			if(!vis[j])
			{
				if(j<val[i])
				{
					ll now=val[i]-j;
					for(auto t:v[i])
					{
						if(cv[t.first]==0&&now)
							now--,ce[t.second]=0,cv[t.first]=1;
					}
				}
				else
				{
					ll now=j-val[i];
					for(auto t:v[i])
					{
						if(cv[t.first]==1&&now)
							now--,ce[t.second]=2,cv[t.first]=0;
					}
				}
				val[i]=j;
				break;
			}
		}
		for(auto j:v[i])
		{
			ll x=j.first;
			vis[val[x]]=false;
		}
	}
	ll ans=0;
	for(int i=1;i<=n;i++)
		ans+=cv[i];
	printf("%lld\n",ans);
	for(int i=1;i<=n;i++)
		if(cv[i])
			printf("%d ",i);
	if(ans)
		puts("");
	for(int i=1;i<=k;i++)
		printf("%lld %lld %lld\n",fr[i],ed[i],ce[i]);
    exit(0);
}