【题解】QOJ 7221. The Road Network

本题被以下比赛引用:

  • ICPCCamp 2016. Day 4. SJTU Dreadnought Contest
  • Moscow International Workshops 2016. Day 3. SJTU Dreadnought Contest

题解:

我们可以把点分成三个集合:\(A,B,C\),其中 \(A\) 是一个团,\(B\) 中每个点和 \(A\)\(1\)\(|A|-1\) 个点有边(内部没有边),\(C\) 中是孤立点。

枚举一个集合中 \(A\) 集合的点数为 \(i\),那么答案一定可以取到 \(i(|A|-i)+\sum_{x\in B} \min\{deg(x),\max\{i,|A|-i\}\}\)。(\(A\) 类点之间的连边 \(+\) \(B\) 类点和 \(A\) 类点的连边)

构造方式即为:一个集合中放前 \(i\) 大的 \(A\) 类点,另一个集合放剩下的 \(A\) 类点和所有的 \(B\) 类点。

接下来考虑计数:

\(C\) 中每个点对方案数的贡献显然是 \(2\),所以说 \(C\) 类点对答案的贡献是 \(2^{|C|}\)

我们现在的目标是保证一个集合取到了能达到答案的 \(A\) 集合的点的个数,然后挪动 \(B\) 类点所在的集合使得答案不变,也就是说需要保证 \(\sum_{x\in B} \min\{deg(x),\max\{i,|A|-i\}\}\) 不发生变化。

\(A\) 集合 \(w\) 最大的点和最小的点分别在集合 \(X,Y\) 的情况下,我们考虑一个集合中 \(B\) 类点 \(x \in Y\) 对于另一个集合中 \(A\) 类点的限制:

  • \(deg(x) > \max\{i,|A|-i\}\) ,集合 \(X\) 只能有 \(w\)\(deg(x)\) 大的点
  • \(deg(x) \leq \max\{i,|A|-i\}\) ,集合 \(X\) 必须有 \(w\)\(deg(x)\) 大的所有点

方案数就是一个组合数。

\(A\) 集合 \(w\) 最大的点和最小的点在同一集合 \(X\) 的情况下,我们考虑 \(B\) 类点 \(x\) 对于另一个集合中 \(A\) 类点的限制:

  • \(deg(x) > \max\{i,|A|-i\}\)\(x\) 放入集合 \(X\),集合 \(Y\) 中只能有 \(w\)\(deg(x)\) 大的点
  • \(deg(x) \leq \max\{i,|A|-i\}\)\(x\) 放入集合 \(Y\),集合 \(X\) 必须有 \(w\)\(deg(x)\) 大的所有点

方案数也是一个组合数

最后还有一些细节:

  • \(i == |A|-i\) 时,因为可以交换两个集合所以需要算两遍
  • \(B\) 中的点的 \(deg\) 非常小时,没有必须放在 \(w\) 最大的点所在集合的另一个集合的限制,所以也可以对 \(B\) 集合放的方案进行镜像,也要算两遍。

时间复杂度 \(O(n^2)\)

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7,NN = 2e3 + 8;

ll n,d;
ll A,B,C;
ll w[NN],deg[NN];
ll fac[NN],inv[NN];
ll ans,f[NN],cnt;

inline ll read(){
	register ll res = 0;
	register char c = getchar();
	while(!isdigit(c)) c = getchar();
	while(isdigit(c)) res = res * 10 + c - 48,c = getchar();
	return res;
}

ll ksm(ll x,ll k){
	ll res = 1;
	while(k){
		if(k & 1) res = res * x % MOD;
		x = x * x % MOD;
		k >>= 1;
	}
	return res;
}
ll binom(int n,int m){
	if(n < m) return 0;
	return fac[n] * inv[m] % MOD * inv[n-m] % MOD; 
}

void init(){
	fac[0] = 1;
	for(int i = 1; i <= n; ++i) fac[i] = fac[i-1] * i % MOD;
	inv[n] = ksm(fac[n],MOD - 2);
	for(int i = n; i >= 1; --i) inv[i-1] = inv[i] * i % MOD;
}
int main(){
	n = read(); d = read(); init();
	for(ll i = 1; i <= n; ++i) w[i] = read();
	sort(w+1,w+1+n,greater<ll>());
	
	A = 1;
	while(A < n && w[A] + w[A+1] >= d) ++A;
	B = A;
	while(B < n && w[B+1] + w[1] >= d) ++B;
	if(A == 1) return printf("0 %lld\n",ksm(2,n)),0;
	//deg 1~A 是 A 类点,deg A+1~B 是 B 类点,deg B+1~n 是 C 类点 
	
	for(ll i = 1; i <= n; ++i){
		for(ll j = 1; j <= n; ++j){
			if(i == j) continue;
			deg[i] += (w[i] + w[j] >= d);
		}
	}
	
	for(ll i = 0; i <= A; ++i){
		ll res = i * (A - i);
		for(ll j = A+1; j <= B; ++j){
			res += min(deg[j], max(i, A - i));
		}
		f[i] = res;
		ans = max(ans,res);
	}//Calc ans
	
	for(ll i = 0; i <= A; ++i) if(ans == f[i]){
		ll l = 0, r = A;
		for(ll j = A+1; j <= B; ++j){
			if(deg[j] > max(i, A-i)) r = min(r,deg[j]);
			else l = max(l,deg[j]);
		}
		cnt = (cnt + binom(r-l, max(i,A-i) - l)) % MOD;
		if(A < B && (deg[A+1] <= min(i, A-i) || i == A-i)) cnt = (cnt+binom(r-l, min(i, A-i)-l)) % MOD;

		if(A < B && deg[A+1] > max(i, A-i) && deg[B] < max(i, A-i)){
			l = 0, r = min(i, A-i);
			for(int j = A+1; j <= B; ++j)
			    if(deg[j] > max(i, A-i)) r = min(r, deg[j] - max(i, A-i));
			    else l = max(l, deg[j]);
			if(l <= r) cnt = (cnt + binom(r-l+max(i, A-i), r-l) * (1+(i == A-i))) % MOD;
		}
	}
	cnt = cnt * ksm(2,n-B) % MOD;
	printf("%lld %lld",ans,cnt);
}
posted @ 2024-01-07 16:23  ricky_lin  阅读(29)  评论(0)    收藏  举报