[HNOI2012]矿场搭建 解题报告

[HNOI2012]矿场搭建 解题报告

题目链接

P3225 HNOI2012矿场搭建 - 洛谷

简要题意

给一个无向图,在图中选择若干特殊点,使得在去除图中任意一个顶点后,每个连通块都有至少一个特殊点。(图中无孤立点)

解题报告

看到“去除顶点”问连通性,我们应当立马想到v-DCC。

我们分类讨论对于一个v-DCC来说,要设置几个特殊点。

  • 如果这个v-DCC中没有割点;也就是说,这个v-DCC其实就是一个连通块。这时我们应当设置两个不同的特殊点。假如只设一个,那么当特殊点被去掉了就没辙了。这是答案增2,方案数乘上 ( n 2 ) \binom{n}{2} (2n)
  • 如果这个v-DCC中有大于等于2个割点,那么这个v-DCC就很好办:不管你去掉哪个点,其他的v-DCC中的点都可以通过割点(即使去掉一个也至少还有一个)到其它与之相连的v-DCC。我们下面会证明,这时一个割点都不需要设。
  • 如果这个v-DCC有且仅有一个割点。如果去掉一个非割点,那么v-DCC中的其他点都可以通过割点到其他v-DCC去;如果去掉割点,那么就被堵死了。所以这时要在非割点处设置一个特殊点。现在我们回到上面的问题:为何情况2一个割点都不要设?因为情况2不论砸掉哪个点,它都可以一直沿着其它的v-DCC往下找,直到找到一个情况3的v-DCC,那里一定设了一个特殊点。

至此,这题就很明朗了。先找v-DCC,然后按照割点数量去分类讨论。

代码

//...
const int MAXN = 1005;
const int MAXE = 1005;
int n, num;
map<int, int> mp;
int head[MAXN], nxt[MAXE], ver[MAXE], cnt;

int dfn[MAXN], low[MAXN], tim;
int dccnum;
int stk[MAXN], stp;
vector<int> dcc[MAXN];
bool iscut[MAXN];

ll ans, f;
void Clear() {
	num = cnt = tim = dccnum = stp = ans = 0;
	f = 1;
	mp.clear();
	memset(head, 0x00, sizeof head);
	memset(dfn, 0x00, sizeof dfn);
	memset(low, 0x00, sizeof low);
	memset(iscut, 0x00, sizeof iscut);
	for(int i = 1; i < MAXN; i++) dcc[i].clear();
}
void addedge(int u, int v) {
	ver[++cnt] = v; nxt[cnt] = head[u]; head[u] = cnt;
}
int getnum(int x) {
	map<int, int>::iterator k = mp.find(x);
	if(k != mp.end()) return k->second;
	mp[x] = ++num;
	return num;
}
void tarjan(int u, int rt) {
	dfn[u] = low[u] = ++tim;
	stk[++stp] = u;
	int fl = 0;
	if(u == rt && !head[u]) {
		dcc[++dccnum].push_back(u);
		return ;
	}
	for(int i = head[u]; i; i = nxt[i]) {
		int v = ver[i];
		if(!dfn[v]) {
			tarjan(v, rt);
			low[u] = min(low[u], low[v]);
			if(dfn[u] <= low[v]) {
				fl++;
				if(u != rt || fl > 1) iscut[u] = 1;
				dccnum++;
				int t;
				do {
					t = stk[stp--];
					dcc[dccnum].push_back(t);
				}while(t != v);//be careful!!! vvvvvvv
				dcc[dccnum].push_back(u);
			}
		} else low[u] = min(low[u], dfn[v]);
	}
}
void work() {
	Clear();
	for(int i = 1; i <= n; i++) {
		int u = read(), v = read();
		u = getnum(u); v = getnum(v);
		addedge(u, v); addedge(v, u);
	}
	for(int i = 1; i <= num; i++)
		if(!dfn[i]) tarjan(i, i);
	for(int i = 1; i <= dccnum; i++) {
		int cnt1 = 0, cnt2 = 0;
		for(auto j : dcc[i]) {
			if(iscut[j]) cnt1++;
			else cnt2++;
		}
		if(cnt1 == 0) {
			ans += 2;
			f *= 1ll * cnt2 * (cnt2 - 1) / 2;
		} else if(cnt1 == 1) {
			ans += 1;
			f *= cnt2;
		}
	}
	printf("%lld %lld\n", ans, f);
}
int main() {
	for(int i = 1; ; i++) {
		n = read(); if(!n) return 0;
		printf("Case %d: ", i);
		work();
	}
	return 0;
}

根据引用\[1\]和引用\[2\]的描述,题目中的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(树状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段树 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日居月诸Rijuyuezhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值