Codeforces 1091E New Year and the Acquaintance Estimation 题解

Codeforces 1091E 解题报告
本文详细解析了 Codeforces 平台上的题目 New Year and the Acquaintance Estimation,介绍了如何利用鄂尔多斯定理解决该问题,并分享了一种 O(nlogn) 的算法思路,通过差分数组优化解决方案。

Codeforces 1091E New Year and the Acquaintance Estimation

【题意】

给你nnn个点的度(d1,d2,...,dn)(d_1,d_2,...,d_n)(d1,d2,...,dn),然后问你第n+1n+1n+1个点的度(dn+1)(d_{n+1})(dn+1)的所有可能值

附上鄂尔多斯定理:∑i=1kdi≤k(k−1)+∑i=k+1nmin⁡(di,k)\sum\limits^{k}_{i=1}d_i\leq k(k-1)+ \sum\limits^n_{i=k+1} \min (d_i,k)i=1kdik(k1)+i=k+1nmin(di,k)

【分析】

首先他们的点的度数和一定为偶数,那么我们就能够知道dn+1d_{n+1}dn+1的奇偶性。

我们观察样例2和4,发现他们的答案都是在相同奇偶性下相邻的,于是我们大胆猜测:如果dn+1=Xd_{n+1}=Xdn+1=X满足条件且dn+1=Yd_{n+1}=Ydn+1=Y满足条件,那么X&lt;Z&lt;YX&lt;Z&lt;YX<Z<Y且与X,YX,YX,Y奇偶性相同的ZZZ也一定符合。

为什么? 很明显,如果XXX符合,那么我们可以把前n个人中的一对(u,v)(u,v)(u,v)的还有关系拆掉,再把uuuvvvn+1n+1n+1构成好友关系(前提是uuuvvv之前与n+1n+1n+1不是好友关系),这样答案就变成了X+2X+2X+2

然后我们的问题就转化成为求出上文中的XXXYYY

枚举不行,我们就去思考二分答案。对于一个dn+1d_{n+1}dn+1,我们可以通过鄂尔多斯定理判断出这个新图是否可能。如果他不行,那么我们需要知道他是大是小。这取决于他是在不等式的左侧还是右侧。如果在左侧,那么这个dn+1d_{n+1}dn+1就太大了,因为如果扩大那么不等式是永远不会成立的;反之也类似。

我们发现上面的定理,如果ddd有序,后面的min(di,k)min(d_i,k)min(di,k)应该是分成两段:一段时间did_idi(did_idi更小),一段时间kkkdid_idi更大)。这个需要前缀和去维护。

于是问题就做完了。

emm 这个是O(nlogn)的吧。

然后我们发现除排序外可以优化O(n)

我们用一个差分数组cf去维护对于每一个kkk,计算哪一段X...YX...YX...Y是符合要求的,然后如果一段区间的cf[X...Y]=ncf[X...Y]=ncf[X...Y]=n则说明这一段通过了所有的要求,就把他加到答案里就好。

呵呵呵虽然时间复杂度依然是O(nlogn)的

【代码】

#include <bits/stdc++.h>
using namespace std ;
#define rep(i, a, b) for (int (i) = (a); (i) <= (b); (i)++)
#define per(i, a, b) for (int (i) = (a); (i) >= (b); (i)--)
#define clr(a) memset(a, 0, sizeof(a))
#define ass(a, sum) memset(a, sum, sizeof(a))
#define lowbit(x) (x & -x)
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define enter cout << endl
#define SZ(x) ((int)x.size())
typedef long long ll ;
typedef unsigned long long ull ;
typedef vector <int> vi ;
typedef vector <ll> vl ;
typedef pair <int, int> pii ;
typedef map <int, int> mii ;
typedef map <string, int> msi ;
const int N = 100010 ;
const int iinf = INT_MAX ;
const ll linf = 2e18 ;
const int MOD = 1000000007 ;
void print(int x) { cout << x << endl ; exit(0) ; }
void PRINT(string x) { cout << x << endl ; exit(0) ; }
void douout(double x){ printf("%lf\n", x + 0.0000000001) ; }

int n ;

signed main(){
    scanf("%d", &n) ; vi a(n), cf(n + 2), res ; vl sum(n + 1) ;
	for (int i = 0; i < n; i++) scanf("%d", &a[i]) ;
	sort(a.begin(), a.end()) ;
	for (int i = 0; i < n; i++) sum[i + 1] = sum[i] + a[i] ;
	int j = 0 ;
	for (int k = 1; k <= n; k++) {
		ll l = sum[n] - sum[n - k], r = 1ll * k * (k - 1) ;
		while (j < n && a[j] < k) j++ ;
		int up = min(n - k, j) ;
		r += sum[up] + 1ll * k * (n - k - up) ; // 前面是那个定理
		int bound = a[n - k] ;
		{ // 分类讨论之后差分
			ll dif = l - r ;
			if (dif <= k && dif <= bound) {
				cf[max(dif, 0ll)]++ ;
				cf[bound + 1]-- ;
			}
		}
		{
			l -= a[n - k] ;
			r += min(a[n - k], k) ;
			ll dif = r - l ;
			if (dif > bound) {
				cf[bound + 1]++ ;
				cf[min(dif + 1, n + 1ll)]-- ;
			}
		}
	}
	int now = 0 ;
	for (int i = 0; i <= n; i++) {
		now += cf[i] ;
		if (now == n && (sum[n] + i) % 2 == 0) res.pb(i) ;
	}
	if (res.empty()) print(-1) ;
	for (int i = 0; i < SZ(res); i++) printf("%d ", res[i]) ; enter ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值