[NOIP 2022] 建造军营 题解

题目

P1 边双缩点

观察样例二,可以发现边双内的边可选可不选。由此考虑边双缩点,Tarjan 找桥即可,缩点后变成一棵树。

P2 设计状态

用最终合法答案形态截这颗树,设计 f u f_u fu 表示 u u u 子树内非空,且子树内军营到 u u u 的边均被保护的方案数。

P3 转移

为方便转移,记 g u g_u gu 表示 u u u 子树空的方案数,遍历 u u u 的儿子 v v v

  • v v v 不选,则 v v v 之前非空, f u × 2 × g v f_u \times 2\times g_v fu×2×gv
  • v v v 选, ( f u + g u ) × f v (f_u+g_u) \times f_v (fu+gu)×fv

g u = ∏ ( 2 × g v ) g_u = \prod(2 \times g_v) gu=(2×gv)

u u u 所在边双点数为 V u V_u Vu,边数为 E u E_u Eu。初值: f u = 2 V u + E u − 2 E u , g u = 2 E u f_u=2^{V_u+E_u}-2^{E_u},g_u=2^{E_u} fu=2Vu+Eu2Eu,gu=2Eu

P4 统计答案

假定只选 i i i 子树内的点,此时子树外的边均可选可不选。然而这样在 i i i 祖先处统计会重复计算 i i i 的贡献,强制不选 i → f a i i \to fa_i ifai 这条边即可,其余子树外的边任意。

P5

#include <iostream>
#include <vector>
#define int long long

using namespace std;

const int N = 5e5 + 5;
const int M = 1e6 + 5;
const int mod = 1e9 + 7;

int n, m, pw[N + M];

struct Edge{
	int to, nxt;
}e1[M << 1], e2[M << 1];

int tot1 = 1, head1[N];
void add1(int u, int v)
{
	e1[++tot1] = {v, head1[u]}; head1[u] = tot1;
}

int tot2 = 1, head2[N];
void add2(int u, int v)
{
	e2[++tot2] = {v, head2[u]}; head2[u] = tot2;
}

int low[N], dfn[N], idx;
bool bridge[M << 1];
void Tarjan(int u, int from)
{
	low[u] = dfn[u] = ++idx;
	for(int i=head1[u]; i; i=e1[i].nxt)
	{
		if((i ^ 1) == from) continue;
		int v = e1[i].to;
		
		if(!dfn[v]) // tree edge
		{
			Tarjan(v, i);
			low[u] = min(low[u], low[v]);
			if(low[v] >= dfn[v])
				bridge[i] = bridge[i ^ 1] = 1;
		}
		else low[u] = min(low[u], dfn[v]); // back edge
	}
}

int cnt, belong[N], V[N], E[N];
void dfs0(int u)
{
	belong[u] = cnt, V[cnt] ++ ;
	for(int i = head1[u]; i; i = e1[i].nxt)
	{
		int v = e1[i].to;
		if(belong[v] or bridge[i]) continue;
		dfs0(v);
	}
}

int ans, siz[N], f[N], g[N];
void dfs(int u, int from)
{
	f[u] = pw[E[u]] * (pw[V[u]] - 1) % mod,
	g[u] = pw[E[u]], siz[u] = E[u];
	for(int i = head2[u]; i; i=e2[i].nxt)
	{
		if((i ^ 1) == from) continue;
		int v = e2[i].to;
		dfs(v, i);
		siz[u] += siz[v] + 1;
		f[u] = f[u] * 2 * g[v] % mod + (f[u] + g[u]) * f[v] % mod;			f[u] %= mod;
		g[u] *= 2 * g[v];					 								g[u] %= mod;
	}
	if(u == 1) ans += f[u], 												ans %= mod;
	else ans += f[u] * pw[m - siz[u] - 1] % mod,							ans %= mod;
}

signed main()
{
	cin >> n >> m;
	
	pw[0] = 1; for(int i=1; i<=m; i++) pw[i] = (pw[i-1] << 1) % mod;
	
	for(int i=1; i<=m; i++)
	{
		int u, v;
		cin >> u >> v;
		add1(u, v); add1(v, u);
	}
	
	Tarjan(1, 0);
	
	for(int i=1; i<=n; i++)
	{
		if(!belong[i]) ++ cnt, dfs0(i);
	}
	
	for(int i=2; i<=tot1; i++)
	{
		int u = e1[i].to, v = e1[i ^ 1].to;
		if(belong[u] == belong[v]) E[belong[u]] ++ ;
		else add2(belong[u], belong[v]);
	}
	
	for(int i=1; i<=cnt; i++) E[i] >>= 1;
	
	dfs(1, 0);
	
	cout << ans;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
NOIP(全国信息学奥林匹克竞赛)2022的测试数据是用来评测选手在竞赛过程中编写的程序的有效性和正确性的。使用测试数据是为了确保选手提交的程序在各种情况下都能正常工作和产生正确的结果。 在NOIP竞赛中,测试数据一般分为两部分:样例数据和评测数据。 样例数据是提供给选手作为示例的测试数据,用来帮助选手理解题目要求和编写程序。一般来说,样例数据是比较简单和容易理解的,覆盖了题目中的各种情况。通过编写和运行程序,选手可以验证自己的程序输出是否与样例数据的答案一致。 评测数据是用来对选手的程序进行严格测试的数据,旨在验证程序的健壮性和正确性。评测数据一般由竞赛组织者提供,经过精心设计和测试,覆盖了各种可能的输入情况。选手需要将自己编写的程序运行在这些评测数据上,并保证输出结果的正确性。 在比赛过程中,选手可以通过阅读题目描述和理解题意,编写自己的程序。接下来,选手可以使用样例数据进行初步测试,确保程序可以正确运行并输出符合预期的结果。然后,选手可以运行评测数据进行更加严格的测试,以验证程序在各种复杂情况下的正确性。 使用NOIP 2022的测试数据可以帮助选手了解题目要求、验证程序的正确性以及提升编程能力。通过不断分析测试数据和程序输出的差异,选手可以找出程序中的问题并进行改进,最终达到解决问题的目的。所以,对于参加NOIP竞赛的选手来说,熟练掌握并灵活运用测试数据是非常重要的。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值