ZOJ 4098 Defense Plan(暴力+剪枝+思維)

題目鏈接:

Defense Plan

 

題意:

王國裏有 n 個城堡,初始防禦力爲 1。每個城堡都可以裝備加農炮,裝備之後第 i 個城堡的防禦力爲 w[i]。設現在每個城堡防禦力爲 t[i],王國的防禦力爲 \prod_{i=1}^{n} t[i] 。此外,還有 m 個互斥條件,表示 i,j 不能同時裝備加農炮。設總共有 k 種不同情況,每種情況王國的防禦力爲xi,則求方差  S^{2}=\frac{(x1-\bar{x})^{2}+(x2-\bar{x})^{2}+...+(xk-\bar{x})^{2}}{k},其中 \bar{x}=\frac{x1+x2+...+xk}{k} 。

 

思路:

先看所求方差:

S^{2}=\frac{(x1-\bar{x})^{2}+(x2-\bar{x})^{2}+...+(xk-\bar{x})^{2}}{k}

=\frac{(x1^{2}+x2^{2}+...+xk^{2})+(\bar{x}^{2}*k)-2\bar{x}(x1+x2+...+xk)}{k}

=(x1^{2}+x2^{2}+...+xk^{2})/k-\bar{x}^{2}

因此,我們需要知道所有情況數 k,所有情況之和 sum,所有情況的平方和 res。

首先,dfs純暴力做法:每個城堡選或者不選(2^{40}),再判斷該情況是否滿足互斥條件(n^{2}),複雜度 O(2^{40}*n^{2}) 。

剪枝:

1. 從第 1 個城堡開始到第 n 個,每次該城堡必選,然後枚舉 i+1 ~ n,把互斥的城堡vis標記都++,再找到後面第一個不互斥的點,2種情況(選或者不選),不選:dfs(i,res,sum,k),選:把其後與其互斥的城堡vis標記都++,dfs(i,res*w[i]*w[i],sum*w[i],k),以此下去,每次把 n 個都遍歷完,更新我們所需要的數值。複雜度 O(2^{40}*n) 。

2. 若第 i 個點與其後面所有點都不互斥,那麼不需要再考慮該點選或不選,直接dfs(i,res+res*w[i]*w[i],sum+sum*w[i],k*2) 。(即兩種情況組合)複雜度:玄學。

題目數據比較水,這樣就能過。但明顯有反例能卡時間,如:1,2,...,n,且 n 與 2,3,...,n-1 互斥,複雜度:O(2^{39}*n)  .

第三個優化:

先預處理每個點與其他點互斥的個數,然後按個數從大到小排序,再按上述兩個優化進行操作。這樣能保證與其他點都不互斥的點儘量在後面,且互斥點越多點在越前面。若與其他點都不互斥的點>=20,那麼dfs複雜度 \leq O(2^{20}*n) ;若與其他點都不互斥的點<20,那麼剩下的點之間就會有依賴關係,至少也能減一半,dfs複雜度也會 \leq O(2^{20}*n)  。因此總複雜度 \leq O(n*(2^{0}+2^{1}+...+2^{20}))\leq O(n*2^{21}) 。

這種解法最後跑出來 20 ms,不知道最快的那個老哥 0 ms 是怎麼跑出來的....

 

Code:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAX = 50;
const ll mod = 1e9 + 7;

typedef struct {
	int id, num;
}Point;

int n, m;
ll w[MAX];
Point p[MAX];
int mp[MAX][MAX];
int vis[MAX];
ll res, sum, k;

bool cmp(Point x, Point y) {
	return x.num > y.num;
}

//擴展歐幾里得求逆元
ll extendGcd(ll a, ll b, ll &x, ll &y) {
	ll ans, t;
	if (b == 0) {
		x = 1; y = 0;
		return a;
	}
	ans = extendGcd(b, a%b, x, y);
	t = x; x = y; y = t - (a / b)*y;
	return ans;
}

ll inv(ll a, ll m) {
	ll x, y, d;
	d = extendGcd(a, m, x, y);
	if (d == 1)
		return (x%m + m) % m;
	else
		return -1;
}

void dfs(int root, ll res1, ll sum1, ll k1)
{
	if (root > n) {
		res = (res + res1) % mod;
		sum = (sum + sum1) % mod;
		k += k1;
		return;
	}
	for (int i = root + 1; i <= n + 1; i++) {
		if (vis[p[i].id])	continue;
		if (i == n + 1) {
			dfs(i, res1, sum1, k1);
			break;
		}
		int fg = 0;
		for (int j = i + 1; j <= n; j++) {
			if (mp[p[i].id][p[j].id]) {
				fg = 1;
				break;
			}
		}
		if (fg == 0) {
			dfs(i, (res1 + res1 * w[p[i].id] % mod*w[p[i].id] % mod) % mod, (sum1 + sum1 * w[p[i].id] % mod) % mod, k1 * 2);
			break;
		}
		dfs(i, res1, sum1, k1);
		for (int j = i + 1; j <= n; j++) {
			if (mp[p[i].id][p[j].id]) {
				vis[p[j].id]++;
			}
		}
		dfs(i, res1*w[p[i].id] % mod*w[p[i].id] % mod, sum1*w[p[i].id] % mod, k1);
		for (int j = i + 1; j <= n; j++) {
			if (mp[p[i].id][p[j].id]) {
				vis[p[j].id]--;
			}
		}
		break;
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &w[i]);
		w[i] %= mod;
	}
	for (int i = 1; i <= m; i++) {
		int x, y;
		scanf("%d%d", &x, &y);
		mp[x][y] = mp[y][x] = 1;
	}
	res = 1;
	sum = 1;
	k = 1;
	//預處理每個點與其他點互斥的個數,排序
	for (int i = 1; i <= n; i++) {
		p[i].id = i;
		p[i].num = 0;	
		for (int j = 1; j <= n; j++) {
			if (mp[i][j]) {
				p[i].num++;
			}
		}
	}
	sort(p + 1, p + n + 1, cmp);
	for (int i = 1; i <= n; i++) {
		memset(vis, 0, sizeof(vis));
		for (int j = i + 1; j <= n; j++) {
			if (mp[p[i].id][p[j].id]) {
				vis[p[j].id]++;
			}
		}
		dfs(i, w[p[i].id] * w[p[i].id] % mod, w[p[i].id], 1);
	}
	//公式計算答案
	ll invk = inv(k, mod);
	ll ans = (res*invk % mod - sum * sum%mod*invk % mod*invk % mod + mod) % mod;
	printf("%lld\n", ans);
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章