CF 1098 题解

上午打比赛的时候感冒了十分难受。。于是发挥非常差。

A

如果偶数层也是确定的话直接让 (a_i = h_i-h_{f_i}) 就好了。

否则我们考虑偶数层是干什么的:它可以让它所有儿子统一减少一个数。于是对于每个点统计出儿子节点的最小值,将这个点赋成最小值即可。

B

考虑如果确定了左上角 (2 imes 2) 的矩阵,那么前两行剩下的格子的每一列的字符集就确定了,贪心分配一下。

然后剩下的行每个 (1 imes 2) 的格子的字符集也确定了 可以选择是否翻转。

C

首先显然二分答案。发现子树答案的最大值是在一条链上,最小值是一个菊花图。

假设限制每个点的儿子个数 (leq k),那么深度为 (x) 的点个数不能超过 (k^{x-1}) 个。( (1) 号点深度为 (1)),我们可以通过调整构造出一组合法的每层有多少个点,输出方案的时候从小到大考虑深度,维护一下当前深度 (-1) 的点还能连多少个点就好了。

D

神仙结论题。。可能只能打表吧?

首先把序列排序 (a_1 leq a_2 leq ldots leq a_n),我们定义一个鱼 (i) 是肥鱼,当且仅当 (a_i > 2sum_{j < i}a_j)

设肥鱼的个数为 (t)。我们来证明答案 (=n-t)

首先我们贪心地想每次肯定要合并两个当前最小的鱼,那么这次如果不拿最小的就会损失掉这个合并后的鱼后来可能的贡献,而如果这次那最小的答案不会变劣(并且会留下更多可操作的余地)

那么按照这个逻辑,可以得到答案 (leq n-t)

我们可以发现 (t leq log V),所以我们将鱼按照 ([2^0,2^1),[2^1,2^2),ldots,[2^{29},2^{30})) 分组,每组里只有至多一个肥鱼并且一定是最小值。从小到大考虑每个肥鱼,由于一定是从小到大合并,每次遇到肥鱼的时候一定是和它前面的若干个鱼的和合并所以一定是不危险的,同组内的合并一定是危险的,而跨组的合并又恰好只会在肥鱼上,所以答案就是 (n-t)

set 维护最小值每次暴力判断即可。复杂度 (O(n log n))

E

中位数问题肯定二分答案 (x) 然后考虑计算 (leq x) 的数的个数。

虽然 (b) 序列的大小是 (O(n^2)) 的,但是 (b) 序列不同的值是 (O(n log n)) 的,用一些技巧处理出来每个值有多少个。

现在相当于算所有子区间的和组成的序列的中位数,我们考虑钦定最小的数 (A) 和最大的数 (B),设出现次数分别为 (CA,CB)(in (A,B)) 的数字的和是 (sm),如果 (A imes CA + B imes CB +sm leq k),那么左右端点可以自由选,答案是 (CA imes CB)

否则我们考虑枚举 (A,B) 分别选了几个,相当于要计算如下式子:

[egin{aligned} sum_{i=1}^{CA}sum_{j=1}^{CB} [iA+jB leq k] &= sum_{i=1}^{CA}sum_{j=1}^{CB} [j leq frac{k-iA}{B}]\ &= sum_{i=1}^{CA} min(max(0,lfloor frac{k-iA}{B} floor),CB) end{aligned} ]

而我们注意到 (min(a,b) = a-max(0,a-b)),所以可以继续拆式子:

[egin{aligned} &=sum_{i=1}^{CA} max(0,lfloor frac{k-iA}{B} floor) - sum_{i=1}^{CA}max(0,max(0,lfloor frac{k-iA}{B} floor)-CB)\ &= sum_{i=1}^{CA} max(0,lfloor frac{k-iA}{B} floor) - sum_{i=1}^{CA} max(0,lfloor frac{k-iA-CB imes B}{B} floor) end{aligned} ]

我们设 (f(n,a,b,c) = sum_{i=0}^{n} max(0,lfloor frac{ai+b}{c} floor )),现在我们要解决算这个的问题。

看起来很像类欧,但是这里的 (a,b) 不一定是正数。

首先如果 (a<0),我们可以将它倒过来算,也就是 (f(n,a,b,c) = f(n,-a,b+na,c))

然后保证 (a geq 0) 了,接下来如果 (b < 0) 那么根据 (ai+b leq 0)可以设 (d=lceil frac{-b}{a} ceil),发现所有 (<d) 的项一定都是 (<0) 的,所以我们可以整体平移 (d),也就是 (f(n,a,b,c) = f(n-d,a,b+da,c))

接下来问题保证了 (a,b geq 0)(max) 也可以去掉了,我们现在要求 (f(n,a,b,c) = sum_{i=0}^n lfloor frac{ai+b}{c} floor)我们来回忆一下类欧怎么做:

  • 如果 (a=0),那么 (f(n,a,b,c) = (n+1)lfloor frac{b}{c} floor)

  • 如果 (a geq m)(b geq m),那么

[egin{aligned} sum_{i=0}^n lfloor frac{ai+b}{c} floor &= sum_{i=0}^n (lfloor frac{(a mod c)i+(b mod c)}{c} floor + lfloor frac{a}{c} floor i+lfloor frac{b}{c} floor ) \ &= f(n,amod c,b mod c,c)+frac{n(n+1)}{2}lfloor frac{a}{c} floor+(n+1)lfloor frac{b}{c} floor end{aligned} ]

  • 如果 (a < m)(b < m),设 (M=lfloor frac{an+b}{c} floor)那么

[egin{aligned} sum_{i=0}^n lfloor frac{ai+b}{c} floor &= sum_{i=0}^n sum_{j=1}^M [j leq lfloor frac{ai+b}{c} floor]\ &= sum_{i=0}^n sum_{j=0}^{M-1}[j+1 leq lfloor frac{ai+b}{c} floor]\ &= sum_{i=0}^n sum_{j=0}^{M-1}[cj+c leq ai+b]\ &= sum_{i=0}^n sum_{j=0}^{M-1}[cj+c < ai+b+1]\ &= sum_{j=0}^{M-1} sum_{i=0}^n[i>lfloor frac{cj+c-b-1}{a} floor]\ &= nM-f(M-1,c,c-b-1,a) end{aligned} ]

发现过程类似于 (gcd) 的辗转相除,所以复杂度是 (O(log n)) 的。

然后我们就可以双指针了:枚举左端点,先找到最后一个右端点满足可以随便选的,然后再往右找到最后一个满足有可能用这两个端点一部分凑出来的;发现 (i) 的第二种情况实际上就是 (i+1) 的第一种情况,所以就可以愉快双指针了。

时间复杂度 (O(n log^2 n))我感觉细节挺多的写了一下午。

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
#define int LL
const int MAXN = 1e5+5;
int a[MAXN],n;
P st[MAXN];
int tp;
LL t[MAXN];
int A[MAXN];
int M;
LL sm[MAXN],csm[MAXN],cnt[MAXN];

inline LL f(LL n,LL a,LL b,LL c){
	if(n < 0) return 0;
	if(a < 0) return f(n,-a,b+n*a,c);
	if(b < 0){
		if(!a) return 0;
		LL d = std::ceil((long double)(-b)/a);
		return f(n-d,a,b+a*d,c);
	}
	if(!a) return (b/c)*(n+1);
	if(a >= c || b >= c) return f(n,a%c,b%c,c)+(a/c)*n*(n+1)/2+(b/c)*(n+1);
	LL M = (a*n+b)/c;
	return n*M-f(M-1,c,c-b-1,a);
}

inline LL calc(int l,int r,LL k){
	if(sm[r]-sm[l-1] <= k) return cnt[l]*cnt[r];
	LL s = sm[r-1]-sm[l];
	LL A = ::A[l],B = ::A[r],CA = cnt[l],CB = cnt[r];
	LL gx = f(CA,-A,k-s,B)-std::max(0ll,(k-s)/B);
	gx -= f(CA,-A,k-B*CB-s,B)-std::max(0ll,(k-B*CB-s)/B);
	return gx;
}

inline bool chk(LL k){// 判断答案是否<=k 如果<=k需要满足 <=k的数 >= >k的数
	// 计算 <= k 的数字数量
	LL res = 0;
	FOR(i,1,M){
		int l = std::min((LL)k/A[i],cnt[i]);// 最多选几个
		res += cnt[i]*l - l*(l-1)/2;
		sm[i] = sm[i-1]+A[i]*cnt[i];
		csm[i] = csm[i-1]+cnt[i];
	}
	int r = 1;
	FOR(l,1,M){// 每次获得最长的区间和<=k的区间
		if(A[l] > k) break;
		r = std::max(r,l+1);
		while(r <= M && sm[r]-sm[l-1] <= k) ++r;
		res += (csm[r-1]-csm[l])*cnt[l];
		while(r <= M && sm[r]-sm[l] <= k) res += calc(l,r,k),++r;
		res += calc(l,r,k);
	}
	LL m = n*(n+1)/2;
	return res >= m*(m+1)/2 - res;
}

signed main(){
	scanf("%lld",&n);
	FOR(i,1,n) scanf("%lld",a+i);
	FOR(i,1,n){
		int p = 0;
		st[++tp] = MP(a[i],i);
		FOR(j,1,tp){
			st[j].fi = std::__gcd(st[j].fi,a[i]);
			if(st[j].fi == st[p].fi) continue;
			st[++p] = st[j];
		}
		tp = p;
		FOR(j,1,tp-1) t[st[j].fi] += st[j+1].se-st[j].se;
		t[st[tp].fi] += i-st[tp].se+1;
	}
	FOR(i,1,MAXN-1) if(t[i]) A[++M] = i,cnt[M] = t[i];
	LL l = 1,r = 1e15,ans = -1;
	while(l <= r){
		LL mid = (l + r) >> 1;
		if(chk(mid)) ans = mid,r = mid-1;
		else l = mid+1;
	}
	printf("%lld
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/rainair/p/14305855.html