洛谷 P3225 [HNOI2012]矿场搭建

题面

边数N<=500的挖煤点,需要设置尽量少的逃生出口,使得某个煤矿倒塌后,其他挖煤点的工人仍然可以通过某条路径到达逃生出口,并给出逃生出口最少设置数量与方案数
多组数据

分析

倒塌一座后…这一点令人想到割点(割点的倒塌会增加连通块数量,产生隔断),所以本题中显然割点是比较重要的一种分析。

先用tarjan跑出割点,发现把出口安放到割点上不是最优的(割点的倒塌会使连通块增加+出口减少,不利)
所以就要分析除割点外的其他部分,如何安放出口。

不难想到,先把割点删去可以造成一系列无割联通块:

方案①:在每个这样的连通块内安放两个出口。
看似最优,其实有反例

如这种情况,无割图123是删去cut1,cut2得到的连通块,不难想到比方案1中更优的情形:无割图内不放出口,无割图2和3内各一个出口。
如果割点倒塌了一个这样仍能保证逃生
如果一个出口倒塌,其内的人可以通过割点走到另一个出口。
所以方案①不是最优

这样,就发现出口的安排和这种无割图的割点连接数有关。
无割图可以考虑成一种缩点,判断每一个无割图连接的割点数,不难发现:

方案②:
如果这个连通块内本无割点,则只需要安排2个出口,为保证即便一个倒塌,其他也能逃生, C n 2 C_{n}^2 Cn2种安排
如果一个无割图连接着一个割点,其内只需安排一个出口,n种安排
无割图连接2个及以上割点,不需安排出口

不难想到,已知割点后再一次dfs,处理出每一个块的size及割点连接数,用组合数学即可计算出种数

代码

#include "cstdlib"
#include <iostream>
#include <stdio.h>
#include <string.h>
#include<algorithm>
#include <string>
#include<vector>
using namespace std;
vector<int> G[510];//i为顶点,G[i]为从其延伸的边集合
int dfs_clock = 0;//时间戳
int pre[510];//每个节点被遍历到时的时间戳
int iscut[510];//记录这个点是不是割点
int low[510];// low[u]表示dfs树中u及其后代经过单线能连回的最早祖先的pre值
int dfs(int u, int fa)//处理u节点的bfs,其父为fa
{
	int lowu = pre[u] = ++dfs_clock;//给u打上编号
	int child = 0;//u的孩子数量
	int v, lowv;//u的某个邻居,这个邻居的low值
	for (int i = 0; i < G[u].size(); i++)
	{
		v = G[u][i];
		if (!pre[v])//v未被访问过,是子节点
		{
			child++;
			lowv = dfs(v, u);
			lowu = min(lowu, lowv);
			if (lowv >= pre[u])iscut[u] = 1;//u的子节点v中没有能连到比u的clock序更前的边,割点
		}
		else if (pre[v] < pre[u] && v != fa)//v已被访问过,且不是u的父亲邻居(u连出的这条边是反向边)
		{
			lowu = min(lowu, pre[v]);
		}
	}
	if (fa == 0 && child == 1)iscut[u] = 0;//根节点且只有一个子,这个根不是割点,2+个子节点则是
	low[u] = lowu;
	return lowu;
}
int id[510];//删去割点后,其余元素属于的连通块编号
int siz[510];//除去割点后,剩余连通块的元素个数
int cutnum[510];//上面这种连通块与多少割点相连
void chunk(int s,int num)//当前节点,当前节点所属连通块
{
	id[s] = num;
	siz[num]++;
	int cur;
	for (int i = 0; i < G[s].size(); i++)
	{
		cur = G[s][i];
		if (id[cur]!=num) {//这一次未被访问过
			if (iscut[cur]) { cutnum[num]++; id[cur] = num; }
			else chunk(cur, num);
		}
	}
}
int max(int &a, int &b, int &c)
{
	if (a > b)
	{
		if (a > c)return a;
		else return c;
	}
	else
	{
		if (b > c)return b;
		else return c;
	}
}
int main()
{
	ios::sync_with_stdio(false);
	int n, m;//点数,边数
	int x, y;
	for (int o = 1;;o++) {
		n = 0;
		cin >> m;
		if (m == 0)break;
		for (int i = 0; i < m; i++)
		{
			cin >> x >> y;
			n = max(x, y, n);
			G[x].push_back(y);
			G[y].push_back(x);
		}
		for (int i = 1; i <= n; i++)
		{
			if (!pre[i])dfs(i, 0);//有多个联通区域
		}
		//iscut[u]==1表示是割点
		int cnt = 0;
		for (int i = 1; i <= n; i++)
		{
			if (iscut[i] == 0 && (!id[i])) { cnt++;chunk(i, cnt); }
		}
		long long ans = 1;
		int c = 0;//安放个数
		for (int i = 1; i <= cnt; i++)
		{
			if (cutnum[i] == 0) { ans *= siz[i] * (siz[i] - 1) / 2; c += 2; }//本身是个无割点连通块
			else if (cutnum[i] == 1) { ans *= siz[i]; c++; }//连接着一个割点的无割图
			else ans *= 1;//连接着>=2个割点的无割图
		}

		cout << "Case " << o << ": " << c << " " << ans<<endl;

		for (int i = 1; i <= n; i++)G[i].clear();
		memset(low, 0, 510 * sizeof(int));
		memset(pre, 0, 510 * sizeof(int));
		memset(iscut, 0, 510 * sizeof(int));
		memset(id, 0, 510 * sizeof(int));
		memset(siz, 0, 510 * sizeof(int));
		memset(cutnum, 0, 510 * sizeof(int));
		dfs_clock = 0;
		//初始化过程
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值