【HNOI2015】实验比较(树形DP,容斥)

20 篇文章 0 订阅
5 篇文章 0 订阅

题意:

给你一棵树,你要对所有节点定一个顺序序列,形如 p 1 ⊕ 1 p 2 ⊕ 2 p 3 ⋯ p n − 1 ⊕ n − 1 p n p_1 \oplus_1 p_2 \oplus_2 p_3\cdots p_{n-1}\oplus_{n-1} p_n p11p22p3pn1n1pn,其中 ⊕ i \oplus_i i = = = < < < p 1 ∼ n p_{1\sim n} p1n 1 ∼ n 1\sim n 1n 的一个排列, 要满足父亲节点严格小于儿子节点,求不同的序列个数,其中两个序列不同当且仅当存在两个点 a , b a,b a,b 在两个序列中的关系( < , = , > <,=,> <,=,>)不同。

n ≤ 100 n\leq 100 n100

题解:

法一:

有点类似拓扑序的 DP?

注意由于一个连续等于段内的点的顺序是无关的,所以我们 DP 的时候只需要记录连续等于段的数量。

f i , j f_{i,j} fi,j 表示仅考虑 i i i 子树内的点的顺序序列,其中构成了 j j j 个连续等于段的方案数。

合并子树时枚举新的连续等于段个数转移即可。时间复杂度为 O ( n 3 ) O(n^3) O(n3),类似一个树上背包,只不过转移是 O ( n ) O(n) O(n) 而不是 O ( 1 ) O(1) O(1) 的。

法二:

考虑将问题转化:现在要求你将每个节点都染上一种颜色,要求父亲节点颜色大于(注意这里为了方便,我们改成了大于)儿子节点颜色,且出现的颜色必须连续,求染色方案数。

考虑枚举出现的颜色的最小值 M M M,现在转化为:给每个节点染上一种 [ 1 , M ] [1,M] [1,M] 中的颜色,要求出现的颜色个数恰好为 M M M,记方案数为 g M g_M gM

考虑容斥,考虑求出 f i f_i fi 表示给每个节点染上一种 [ 1 , i ] [1,i] [1,i] 中的一种颜色,没有其他限制的方案数。那么:
g M = ∑ i = 0 M ( M i ) ( − 1 ) M − i f i g_M=\sum_{i=0}^M\binom{M}{i}(-1)^{M-i}f_i gM=i=0M(iM)(1)Mifi
f i f_i fi 可以容易用树形 DP 求出。时间复杂度 O ( n 2 ) O(n^2) O(n2)

#include<bits/stdc++.h>

#define N 110
#define fi first
#define se second
#define pii pair<int,int>
#define mk(a,b) make_pair(a,b)

using namespace std;

namespace modular
{
	const int mod=1000000007,inv2=(mod+1)/2;
	inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
	inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
	inline int mul(int x,int y){return 1ll*x*y%mod;}
	inline void Add(int &x,int y){x=x+y>=mod?x+y-mod:x+y;}
	inline void Dec(int &x,int y){x=x-y<0?x-y+mod:x-y;}
	inline void Mul(int &x,int y){x=1ll*x*y%mod;}
}using namespace modular;

inline int poww(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1) Mul(ans,a);
		Mul(a,a);
		b>>=1;
	}
	return ans;
}

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

int n,m,fa[N];
int cnt,head[N],nxt[N],to[N];
int f[N][N],s[N][N],g[N];
int fac[N],ifac[N];
bool vis[N],nrt[N];

int C(int n,int m)
{
	return mul(mul(fac[n],ifac[m]),ifac[n-m]);
}

vector<pii>e;

int find(int x)
{
	return x==fa[x]?x:(fa[x]=find(fa[x]));
}

void adde(int u,int v)
{
	nrt[v]=1;
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
}

void dfs(int u)
{
	if(vis[u])
	{
		puts("0");
		exit(0);
	}
	vis[u]=1;
	for(int j=1;j<=n+1;j++) f[u][j]=1;
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		dfs(v);
		for(int j=1;j<=n+1;j++)
		{
			if(u!=n+1) Mul(f[u][j],s[v][j-1]);
			else Mul(f[u][j],s[v][j]);
		}
	}
	for(int j=1;j<=n+1;j++)
		s[u][j]=add(s[u][j-1],f[u][j]);
}

int main()
{
//	freopen("1.in","r",stdin);
//	freopen("1_my.out","w",stdout);
	n=read(),m=read();
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		int u=read();
		char c=getchar();
		int v=read();
		if(c=='<') e.push_back(mk(u,v));
		else
		{
			int a=find(u),b=find(v);
			if(a!=b) fa[a]=b;
		}
	}
	for(auto ne:e) adde(find(ne.fi),find(ne.se));
	for(int i=1;i<=n;i++)
		if(fa[i]==i&&!nrt[i]) adde(n+1,i);
	dfs(n+1);
	for(int i=1;i<=n;i++)
	{
		if(fa[i]==i&&!vis[i])
		{
			puts("0");
			return 0;
		}
	}
	fac[0]=1;
	for(int i=1;i<=n+1;i++) fac[i]=mul(fac[i-1],i);
	ifac[n+1]=poww(fac[n+1],mod-2);
	for(int i=n+1;i>=1;i--) ifac[i-1]=mul(ifac[i],i);
	int ans=0;
	for(int i=1;i<=n+1;i++)
	{
		for(int j=1;j<=i;j++)
		{
			if((i-j)&1) Dec(g[i],mul(C(i,j),s[n+1][j]));
			else Add(g[i],mul(C(i,j),s[n+1][j]));
		}
		Add(ans,g[i]);
	}
	printf("%d\n",mul(ans,inv2));
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值