【SDOI2019】热闹的聚会与尴尬的聚会(乱搞)(贪心)

传送门


题解:

其实仔细看一下发现是要使得 n < ( p + 1 ) ( q + 1 ) n<(p+1)(q+1) n<(p+1)(q+1),并且 P , Q P,Q P,Q两个集合互不相关。

那么其实问题就是找一个尽可能大的独立集(众所周知最大独立集是NP-Hard)和一个最小度数尽可能大的子图。

第一反应:这怎么没有输出-1???一定有解???

确实是一定有解。

我在LOJ提交记录里面看到了好多乱搞,比如说什么随机化,还有用bitset的。。。

典型乱搞题你值得拥有

实际上这道题有复杂度稳定的且保证解正确的高效做法。

首先求一个极大独立集,我们考虑如下方式:

每次从残余子图中选择一个度数最小的点,把它和它相邻的所有点删除,设它的度数为 d d d,则这次会删除 d + 1 d+1 d+1个点,当前图中最小度数就是 d d d

找到最大的 d d d,所有该时刻后删除的点全部加到 P P P集合中,所有作为最小度数被选择过的点加入 Q Q Q集合,显然这个时候 p = d p=d p=d,且由于每次至多删除 d + 1 d+1 d+1个点,有 n ≤ ( d + 1 ) ∣ q ∣ n\leq (d+1)|q| n(d+1)q,满足题意。

找度数最小的点我写的ZKW线段树。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	template<typename T>
	inline T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int gi(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;

cs int N=1e4+7,M=1e5+7;

int n,m,tim,mx;
int d[N],del[N],vis[N];
std::vector<int> G[N];

namespace SGT{
	int a[1<<15|7],M;
	inline int Min(int a,int b){return d[a]<d[b]?a:b;}
	inline void init(int n){
		for(M=1;M<=n+1;M<<=1);
		for(int re i=M;i<=M+M-1;++i)a[i]=(1<=i-M&&i-M<=n)?i-M:0;
		for(int re i=M-1;i;--i)a[i]=Min(a[i<<1],a[i<<1|1]);
	}
	inline void update(int p){for(p+=M;p>>=1;)a[p]=Min(a[p<<1],a[p<<1|1]);}
	inline int top(){return a[1];}
}

inline void solve(){
	n=gi(),m=gi();
	memset(d+1,0,n<<2);
	memset(del+1,0,n<<2);
	memset(vis+1,0,n<<2);
	for(int re i=1;i<=n;++i)G[i].clear();
	for(int re i=1;i<=m;++i){
		int u=gi(),v=gi();
		++d[u],++d[v];
		G[u].push_back(v);
		G[v].push_back(u);
	}
	SGT::init(n);tim=mx=0;int ps=0;
	while(true){
		int u=SGT::top();if(d[u]==1e9)break;
		if(d[u]>=mx)mx=d[u],ps=tim;
		vis[u]=1;del[u]=++tim,d[u]=1e9,SGT::update(u);
		for(int re v:G[u])if(!del[v]){
			del[v]=tim;d[v]=1e9,SGT::update(v);
			for(int re w:G[v])if(!del[w])--d[w],SGT::update(w);
		}
	}
	std::vector<int> P,Q;
	for(int re i=1;i<=n;++i){
		if(del[i]>ps)P.push_back(i);
		if(vis[i])Q.push_back(i);
	}
	cout<<P.size()<<"\n";
	for(int re u:P)cout<<u<<" ";cout<<"\n";
	cout<<Q.size()<<"\n";
	for(int re v:Q)cout<<v<<" ";cout<<"\n"; 
}

signed main(){
#ifdef zxyoi
	freopen("party.in","r",stdin);
#endif
	d[0]=1e9;int T=gi();while(T--)solve();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值