[SDOI2019] 热闹的聚会与尴尬的聚会

problem

luogu-P5361

他的联系薄上有 n n n 位好友,他们两两之间或者互相认识,或者互相不认识。

小 Q 希望在周六办一个热闹的聚会,再在周日办一个尴尬的聚会。

  • 一场热闹度为 p p p 的聚会请来了任意多位好友,对于每一位到场的好友来说都有至少 p p p 位他认识的好友也参加了聚会,且至少对于一位到场的好友来说现场恰好有 p p p 位他认识的好友;
  • 一场尴尬度为 q q q 的聚会请来了恰好 q q q 位好友,且他们两两互不认识。

两场聚会可能有重复的参与者,联系薄上也有可能有某些好友同时缺席了两场聚会。

小 Q 喜欢周六聚会的热闹度 p p p 与周日聚会的尴尬度 q q q 之间满足: ⌊ n p + 1 ⌋ ≤ q \left\lfloor \frac{n}{p+1} \right\rfloor\le q p+1nq ⌊ n q + 1 ⌋ ≤ p \left\lfloor \frac{n}{q+1} \right\rfloor \le p q+1np

请帮助小 Q 找出一个可行的邀请方案。

solution

observation1 : \text{observation1}: observation1: ⌊ n p + 1 ⌋ ≤ q ∧ ⌊ n q + 1 ⌋ ≤ p ⇔ ( p + 1 ) ( q + 1 ) ≥ n + 1 \lfloor\frac{n}{p+1}\rfloor\le q\wedge \lfloor\frac{n}{q+1}\rfloor\le p\Leftrightarrow (p+1)(q+1)\ge n+1 p+1nqq+1np(p+1)(q+1)n+1

我们只需要分别最大化 p , q p,q p,q 即可。

看似是两个独立的部分,实际上他们各自的构造方式是一样的原理。

  • 热闹的聚会。即度数限制的图。

每次找出当前图中度数最小的点,更新 p p p 的最大值。

并删掉这个点,动态地改变与之相连的其余点的度数。

将弹出的编号桉顺序记录下来,并记下最大值的位置。

位置以前的点则是不被选的。

  • 尴尬的聚会。即独立集。

    每次找出当前图中度数最小的且未被标记的点,加入尴尬的聚会。

    然后标记与之直接相连的所有点都不能参加聚会。

下面给出该构造的正确性证明:

将每个点加入尴尬的聚会,除去这个点本身,最多会从图中删掉 p p p 个点。显然 q ≥ ⌈ n p + 1 ⌉ q\ge \lceil\frac{n}{p+1}\rceil qp+1n

如果独立集的选点运行了 q q q 次,第 i i i 次删掉的点度数为 d i d_i di

则有 ∑ i = 1 q ( d i + 1 ) ≥ n \sum_{i=1}^q(d_i+1)\ge n i=1q(di+1)n。而 ( q + 1 ) ⋅ max ⁡ ( d i + 1 ) ≥ n (q+1)·\max(d_i+1)\ge n (q+1)max(di+1)n 显然。

code

#include <bits/stdc++.h>
using namespace std;
#define maxn 10005
#define Pair pair < int, int >
int T, n, m;
int p[maxn], q[maxn], vis[maxn], deg[maxn], d[maxn];
vector < int > G[maxn];
priority_queue < Pair, vector < Pair >, greater < Pair > > Q;

int main() {
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d %d", &n, &m );
		for( int i = 1;i <= n;i ++ ) deg[i] = 0;
		for( int i = 1;i <= n;i ++ ) G[i].clear();
		for( int i = 1, u, v;i <= m;i ++ ) {
			scanf( "%d %d", &u, &v );
			G[u].push_back( v );
			G[v].push_back( u );
			deg[u] ++, deg[v] ++;
		}
		
		int ans1 = 0, cnt1 = 0, pos;
		for( int i = 1;i <= n;i ++ ) vis[i] = 0;
		for( int i = 1;i <= n;i ++ ) d[i] = deg[i];
		for( int i = 1;i <= n;i ++ ) Q.push( make_pair( d[i], i ) );
		while( ! Q.empty() ) {
			int u = Q.top().second, w = Q.top().first; Q.pop();
			if( vis[u] ) continue; 
			vis[u] = 1;
			p[++ cnt1] = u;
			if( w > ans1 ) ans1 = w, pos = cnt1;
			for( int v : G[u] ) if( ! vis[v] ) Q.push( make_pair( --d[v], v ) );
		}
		
		int ans2 = 0, cnt2 = 0;
		for( int i = 1;i <= n;i ++ ) vis[i] = 0;
		for( int i = 1;i <= n;i ++ ) d[i] = deg[i];
		for( int i = 1;i <= n;i ++ ) Q.push( make_pair( d[i], i ) );
		while( ! Q.empty() ) {
			int u = Q.top().second, w = Q.top().first; Q.pop();
			if( vis[u] ) continue;
			vis[u] = 1;
			q[++ cnt2] = u;
			for( int v : G[u] ) vis[v] = 1;
		}
		
		for( int i = 1;i <= n;i ++ ) vis[i] = 0;
		for( int i = 1;i <= pos;i ++ ) vis[p[i]] = 1;
		printf( "%d ", n - pos );
		for( int i = 1;i <= n;i ++ ) if( ! vis[i] ) printf( "%d ", i );
		puts("");
		printf( "%d ", cnt2 );
		for( int i = 1;i <= cnt2;i ++ ) printf( "%d ", q[i] );
		puts("");
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值