Luogu5405 CTS2019氪金手游(容斥原理+树形dp)

  考虑外向树怎么做。显然设f[i][j]为i子树中出现权值和为j的合法方案的概率,转移做树形背包即可。

  如果树上只有一条反向边,显然可以先不考虑该边计算概率,再减去将整棵树看做外向树的概率。于是考虑容斥,进一步拓展到多条反向边,就是考虑0条反向边的概率-考虑1条反向边的概率+考虑2条反向边的概率……容斥可以在dp中完成,即遇到反向边时分是否考虑它转移,若考虑乘上-1的系数。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 3010
#define P 998244353
char getc(){char c=getchar();while ((c<‘A‘||c>‘Z‘)&&(c<‘a‘||c>‘z‘)&&(c<‘0‘||c>‘9‘)) c=getchar();return c;}
int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
int read()
{
	int x=0,f=1;char c=getchar();
	while (c<‘0‘||c>‘9‘) {if (c==‘-‘) f=-1;c=getchar();}
	while (c>=‘0‘&&c<=‘9‘) x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}
int n,a[N][4],p[N],size[N],I[N],ans,t;
int d[N][N],f[N][N],h[N];
bool flag[N];
int ksm(int a,int k)
{
	int s=1;
	for (;k;k>>=1,a=1ll*a*a%P) if (k&1) s=1ll*s*a%P;
	return s;
}
int inv(int a){return ksm(a,P-2);}
void inc(int &x,int y){x+=y;if (x>=P) x-=P;}
struct data{int x,y,op;
}e[N];
struct data2{int to,nxt,op;
}edge[N];
void addedge(int x,int y,int op){t++;edge[t].to=y,edge[t].nxt=p[x],edge[t].op=op,p[x]=t;}
void dfs(int k)
{
	flag[k]=1;
	for (int i=1;i<=n;i++)
	if (d[k][i]&&!flag[i]) d[k][i]=d[i][k]=k,dfs(i);
}
void dp(int k)
{
	f[k][0]=1;
	for (int i=p[k];i;i=edge[i].nxt)
	{
		dp(edge[i].to);
		for (int x=size[k];x>=0;x--) h[x]=f[k][x];
		for (int x=0;x<=size[k]+size[edge[i].to];x++) f[k][x]=0;
		if (edge[i].op==0)
		{
			for (int x=size[k];x>=0;x--)
				for (int y=size[edge[i].to];y>=0;y--)
				inc(f[k][x+y],1ll*h[x]*f[edge[i].to][y]%P);
		}
		else
		{
			for (int x=size[k];x>=0;x--)
				for (int y=size[edge[i].to];y>=0;y--)
				inc(f[k][x+y],1ll*(P-1)*h[x]%P*f[edge[i].to][y]%P),
				inc(f[k][x],1ll*h[x]*f[edge[i].to][y]%P);
		}
		size[k]+=size[edge[i].to];
	}
	for (int x=size[k];x>=0;x--) h[x]=f[k][x];
	for (int x=0;x<=size[k]+3;x++) f[k][x]=0;
	for (int x=size[k];x>=0;x--)
		for (int y=3;y>=1;y--)
		inc(f[k][x+y],1ll*h[x]*a[k][y]%P*y*I[x+y]%P);
	size[k]+=3;
}
int main()
{
#ifndef ONLINE_JUDGE
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	const char LL[]="%I64d\n";
#else
	const char LL[]="%lld\n";
#endif
	n=read();
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=3;j++) a[i][j]=read();
		int p=inv(a[i][1]+a[i][2]+a[i][3]);
		for (int j=1;j<=3;j++) a[i][j]=1ll*a[i][j]*p%P;
	}
	for (int i=1;i<n;i++) e[i].x=read(),e[i].y=read(),d[e[i].x][e[i].y]=d[e[i].y][e[i].x]=1;
	dfs(1);
	for (int i=1;i<n;i++)
	{
		if (d[e[i].x][e[i].y]==e[i].y) swap(e[i].x,e[i].y),e[i].op=1;
		addedge(e[i].x,e[i].y,e[i].op);
	}
	for (int i=1;i<=n*3;i++) I[i]=inv(i);
	dp(1);
	for (int i=0;i<=3*n;i++) inc(ans,f[1][i]);
	cout<<ans;
	return 0;
}

  

原文地址:https://www.cnblogs.com/Gloid/p/10907852.html

时间: 2024-07-29 07:57:03

Luogu5405 CTS2019氪金手游(容斥原理+树形dp)的相关文章

[CTS2019]氪金手游(容斥+树形背包DP)

降智好题.本蒟蒻VP时没想到怎么做被题面迷惑了,只会20分的“好”成绩.简直自闭了. 首先显然度为0的点是白给的,根据等比数列求和公式即可求得.然后考虑这个树如果是一颗外向树,就是每个点先父亲再自己.然后直接DP,令f[i][j]表示子树i内Σw=j的概率,转移时直接用背包转移一发即可.边是正向的直接转移,反向的加上去掉该限制的答案,并减去反向的答案.复杂度显然是O(n2) #include<bits/stdc++.h> using namespace std; const int N=101

p5405 [CTS2019]氪金手游

题目大意 题意狗屁不通 看毛子语都比看这个题面强 分析 我们假设这棵树是一个内向树 那么我们可以轻易的得到dp[x][i]表示x点子树和为i的期望 转移只需枚举当前期望大小和子树期望大小即可 但是由于边的方向不一定 所以这棵树上存在反向边 我们可以容斥有i个边不合法的情况 因此对于一个反向边要么x点加上关系合法,将子树分离的贡献 要么这个边算是不合法的 对于这种情况我们可以直接减掉贡献 因为我们知道这个贡献已经是0~i的容斥情况 而这个减号相当于*-1 可以完成容斥 复杂度O(n^2) 代码 #

洛谷 P2986 [USACO10MAR]Great Cow Gat…(树形dp+容斥原理)

P2986 [USACO10MAR]伟大的奶牛聚集Great Cow Gat… 题目描述 Bessie is planning the annual Great Cow Gathering for cows all across the country and, of course, she would like to choose the most convenient location for the gathering to take place. Each cow lives in on

算法进阶面试题05——树形dp解决步骤、返回最大搜索二叉子树的大小、二叉树最远两节点的距离、晚会最大活跃度、手撕缓存结构LRU

接着第四课的内容,加入部分第五课的内容,主要介绍树形dp和LRU 第一题: 给定一棵二叉树的头节点head,请返回最大搜索二叉子树的大小 二叉树的套路 统一处理逻辑:假设以每个节点为头的这棵树,他的最大搜索二叉子树是什么.答案一定在其中 第一步,列出可能性(最难部分) 1.可能来自左子树上的某课子树 2.可能来自右子树上的某课子树 3.整颗都是(左右子树都是搜索二叉树并且左子树最大小于该节点,右子树最小大于该节点) 第二步,收集信息: 1.左树最大搜索子树大小 2.右树最大搜索子树大小 3.左树

如何快速优化手游性能问题?从UGUI优化说起

WeTest 导读 本文作者从自身多年的Unity项目UI开发及优化的经验出发,从UGUI,CPU,GPU以及unity特有资源等几个维度,介绍了unity手游性能优化的一些方法. 在之前的文章<手游内存占用过高?如何快速定位手游内存问题>中提到,Mono内存和native内存是PSS内存主要的组成部分,mono内存更多的起到内存调用的功能,因此常常成为了开发人员优化内存的起点:而在游戏的其他的进程中,同样有很多因素影响着游戏的性能表现.本文将从UGUI的优化角度,介绍unity游戏性能优化的

洛谷P3047 [USACO12FEB]Nearby Cows(树形dp)

P3047 [USACO12FEB]附近的牛Nearby Cows 题目描述 Farmer John has noticed that his cows often move between nearby fields. Taking this into account, he wants to plant enough grass in each of his fields not only for the cows situated initially in that field, but

青云的机房组网方案(简单+普通+困难)(虚树+树形DP+容斥)

题目链接 1.对于简单的版本n<=500, ai<=50 直接暴力枚举两个点x,y,dfs求x与y的距离. 2.对于普通难度n<=10000,ai<=500 普通难度解法挺多 第一种,树形dp+LCA 比赛的时候,我猜测对于不为1的n个数,其中两两互质的对数不会很多,肯定达不到n^2 然后找出所有互质的对数,然后对为1的数进行特殊处理.(初略的估计了下,小于500的大概有50个质数,将n个数平均分到这些数中,最后大概有10000*50*200=10^7) 对所有的非1质数对,采用离

[vijos1880]选课&lt;树形dp&gt;

题目链接:https://www.vijos.org/p/1180 这是一道树形dp的裸题,唯一的有意思的地方就是用到了多叉树转二叉树 然后本蒟蒻写这一道水题就是因为以前知道这个知识点但是没有怎么去实现,所以就写了这一道题来练一练手 将这道题的多叉树转换成二叉树后,接着就是状态转移方程了 我们先定义数组dp[i][j]表示第i门课,还可以选j门 然后我们可以想到,第i门课我们可以不选,如果不选,就不能去找i的左儿子,但是可以找i的右儿子,即i的右儿子(兄弟)选了j门 加入选了i,那么状态来源就是

手游热更新方案--Unity3D下的CsToLua技术

WeTest 导读 CsToLua工具将客户端 C#源码自动转换为Lua,实现热更新,本文以麻将项目为例介绍客户端技术细节. 麻将项目架构 其中ChinaMahjong-CSLua为C#工程,实现麻将项目的主要业务流程.翻译工程的输入是C#项目生成的dll文件.其中Cecil负责分析类型 类成员关系 ,比如类字段函数结构,引用关系.类之间的继承关系等,ILSpy负责反编译函数体里的语句,比如条件语句,函数调用,算数运算等.下面逐个介绍具体的实现. Mono.Cecil Mono.Cecil:一个