首页 > 其他分享 >DP mix 容斥

DP mix 容斥

时间:2024-03-21 19:13:43浏览次数:31  
标签:return int 容斥 mix dp ans include DP mod

简介

在一类题中,我们需要用 dp 求答案,最后再熔池算出答案,这样复杂度与 dp 有关。

但是我们也可以将容斥系数直接套进 dp 里,这样可以减少一维状态。

例题

P4099 [HEOI2013] SAO

题意: 一棵树,但是边有方向,求拓扑序方案数。

思路:

如果这棵树是内向树或外向树,显然我们可以用 dp 求解。

所以我们考虑计算外向树,然后把所有不符合条件的边容斥掉。不妨设 \(f_{i,j,k}\) 表示 \(i\) 的子树内 \(j\) 条边由不符合反转成符合,\(i\) 所在连通块大小为 \(k\)。

显然是 \(O(n^3)\),我们发现最后的答案是 \(\sum (-1)^jf_{rt, j, k}\),所以我们可以考虑将 \(j\) 这一维代入 dp 值里。

设 \(g_{i,k} = \sum (-1)^j f_{i,j,k}\),则我们考虑对 \(g\) 进行 dp。

我们可以先把 \(f\) 的转移写出来:

如果这条边符合:

\[\binom{k + k' - 1}{k'}f(x,j,k)f(y,j',k') \to F(x,j + j', k + k') \]

如果不符合:

\[\binom{k + k' - 1}{k'}f(x,j,k)f(y,j',k') \to F(x,j + j' + 1, k + k') \]

\[\frac{1}{k'!}f(x,j,k)f(y,j',k') \to F(x,j + j', k) \]

发现这就是类似卷积的形式,于是我们可以将 \(g\) 的转移写出来:

\[\binom{k + k' - 1}{k'}g(x,k)g(y,k') \to G(x,k + k') \]

\[(-1)\binom{k + k' - 1}{k'}g(x,k)g(y,k') \to G(x,k + k') \]

\[\frac{1}{k!}g(x,k)g(y,k') \to G(x,k) \]

然后我们就做完了。

点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1005;
const int mod = 1e9 + 7;

int fpow(int a, int b, int p) {
	if (b == 0)
		return 1;
	int ans = fpow(a, b / 2, p);
	ans = 1ll * ans * ans % p;
	if (b % 2 == 1)
		ans = 1ll * a * ans % p;
	return ans;
}

int fac[N] = {0}, inv[N] = {0};
int cmb[N][N] = {{0}};

void init(int n) {
	fac[0] = 1;
	for (int i = 1; i <= n; i++)
		fac[i] = 1ll * fac[i - 1] * i % mod;
	inv[n] = fpow(fac[n], mod - 2, mod);
	for (int i = n - 1; i >= 0; i--)
		inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
	cmb[0][0] = 1;
	for (int i = 1; i <= n; i++) {
		cmb[i][0] = 1;
		for (int j = 1; j <= i; j++)
			cmb[i][j] = (cmb[i - 1][j] + cmb[i - 1][j - 1]) % mod;
	}
}

int n;
struct Edge {
	int to, val;
	Edge (int _to = 0, int _val = 0) :
		to(_to), val(_val) {}
}; 
vector<Edge> e[N];

int f[N][N] = {{0}};
int g[N][N] = {{0}};
int sz[N] = {0};

void add(int &x, int y) {
	x = (x + y) % mod;
} 

int srh(int x, int pr) {
	for (auto i: e[x])
		if (i.to != pr)	
			srh(i.to, x);
	//初始化
	f[x][1] = sz[x] = 1;
	//转移
	for (auto i: e[x]) {
		int v = i.to, w = i.val;
		if (v == pr)
			continue;
        for (int j = 1; j <= sz[x] + sz[v]; j++)	
			g[x][j] = 0;
		if (w == 1) {
			for (int j = 1; j <= sz[x]; j++)
				for (int k = 1; k <= sz[v]; k++)
					add(g[x][j + k], 1ll * f[x][j] * f[v][k] % mod * cmb[j + k - 1][k] % mod);
		}
		else {
			for (int j = 1; j <= sz[x]; j++)
				for (int k = 1; k <= sz[v]; k++) {
					add(g[x][j + k], 1ll * (mod - 1) * f[x][j] % mod * f[v][k] % mod * cmb[j + k - 1][k] % mod);
					add(g[x][j], 1ll * f[x][j] * f[v][k] % mod * inv[k] % mod);
				}
		} 
		sz[x] += sz[v];
		for (int j = 1; j <= sz[x]; j++)
			f[x][j] = g[x][j];
	//	cout << "after " << x << " " << v << endl;
	//	for (int j = 1; j <= sz[x]; j++)
   //	     	cout << x << " " << j << " " << f[x][j] << endl;	
	} 
    
	return sz[x];
}

void slv() {
	cin >> n;
	init(n);
	char w;
	for (int i = 1; i <= n; i++)
		e[i].clear();
	for (int i = 1, u, v; i < n; i++) {
		cin >> u >> w >> v;
		u++, v++;
		e[u].push_back(Edge(v, (w == '<')));
		e[v].push_back(Edge(u, (w == '>')));
	}
	for (int i = 0; i <= n; i++)
		for (int j = 0; j <= n; j++)
			f[i][j] = 0;
	srh(1, 0);
	int ans = 0;
	for (int k = 1; k <= n; k++)
		add(ans, 1ll * fac[n] * inv[k] % mod * f[1][k] % mod);
	cout << ans << endl;
}

int main() {
	int T;
	cin >> T;
	while (T--)
		slv();
	return 0;
} 
[ARC101E] Ribbons on Tree

题意: 一棵树,求多少种方案将顶点两两配对,使得将每个点对的路径上的所有边全部系上丝带后满足,每条边都有至少一条丝带。

思路:

将 \(n\) 个点两两配对的方案数是所有小于 \(n\) 的奇数的乘积。这个很容易就可以推出来。

如果我们钦定哪些边不覆盖,方案数很好计算,所以我们考虑容斥,\(F(i,j,k)\) 表示 \(i\) 子树内,有 \(j\) 条边未覆盖,其他任意,当前与 \(i\) 联通的点有 \(k\) 个,求方案数。

接着就是和上道题几乎一样的容斥与转移。

点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5005;
const int mod = 1e9 + 7;

int fpow(int a, int b, int p) {
	if (b == 0)
		return 1;
	int ans = fpow(a, b / 2, p);
	ans = 1ll * ans * ans % p;
	if (b % 2 == 1)
		ans = 1ll * a * ans % p;
	return ans; 
}

int n;
vector<int> e[N];
int f[N][N] = {{0}}, g[N][N] = {{0}};

int h[N] = {0}, inv[N] = {0};

void add(int &x, int y) {
	x = (x + y) % mod;
}

void init() {
	h[1] = 1, h[2] = 1;
	for (int i = 3; i <= n; i++)
		h[i] = (i % 2 == 1 ? 1 : 1ll * (i - 1) * h[i - 2] % mod);
	for (int i = 1; i <= n; i++)
		inv[i] = fpow(h[i], mod - 2, mod);
} 

int sz[N] = {0};

void srh(int x, int pr) {
	for (auto i: e[x])
		if (i != pr)
			srh(i, x);
	sz[x] = f[x][1] = 1;
	for (auto i: e[x])
		if (i != pr) {
			for (int j = 1; j <= sz[x]; j++)
				g[x][j] = 0;
			for (int j = 1; j <= sz[x]; j++)
				for (int k = 1; k <= sz[i]; k++) {
					if (k % 2 == 0)
						add(g[x][j], 1ll * f[x][j] * f[i][k] % mod * (mod - 1) % mod);
					add(g[x][j + k], 1ll * f[x][j] * f[i][k] % mod * h[j + k] % mod * inv[j] % mod * inv[k] % mod);
				}
			sz[x] += sz[i];
			for (int j = 1; j <= sz[x]; j++)
				f[x][j] = g[x][j];
		}		
}

int main() {
	cin >> n;
	for (int i = 1, u, v; i < n; i++) {
		cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
	}	
	init();
	srh(1, 0);
	int ans = 0;
	for (int i = 2; i <= n; i += 2)
		add(ans, f[1][i]);
	cout << ans << endl;
	return 0;
}

标签:return,int,容斥,mix,dp,ans,include,DP,mod
From: https://www.cnblogs.com/rlc202204/p/18088064

相关文章

  • 面向报文的UDP(User Datagram Protocol,用户数据报协议)的一个重要特点
    与TCP(TransmissionControlProtocol,传输控制协议)不同,UDP是一种无连接的协议,它不会为数据建立和维护一个持续的连接。因此,UDP的数据传输方式是面向报文的,也就是说,它会把应用层交给它的报文作为一个整体发送出去,不会进行分割或合并。具体来说,当应用层数据交给UDP后,UDP会为其......
  • 龙迅#LT8712SX适用于Type-C/DP1.4转两路Type-C/DP1.4/HDMI2.0应用方案,支持MST和SST功
    1.描述LT8712SX是一款高性能Type-C/DP1.4转Type-C/DP1.4/HD-DVI2.0/DP++转换器,具有两个可配置的DP1.4/HD-DVI2.0输出接口和音频输出接口。LT8712SX支持DisplayPort™单流传输(SST)模式和多流传输(MST)模式。当接收通过单个DP链路打包和传输的多个视频/音频流时,LT8712SX......
  • [DPDK]Linux平台上DPDK入门指南(一)
    [DPDK]Linux平台上DPDK入门指南(一)1.1简介1.1.1文档地图1.2系统要求1.2.1X86上预先设置BIOS1.2.2编译DPDK1.2.3运行DPDK应用程序系统软件在Linux环境中使用Hugepages预留Hugepages给DPDK使用DPDK使用Hugepages配置内存用于DPDK使用1.3使用源码编译DPDK......
  • QT网络编程之实现UDP广播发送和接收
    一.UDP广播介绍UDP广播地址固定IP地址为:XXX.XXX.XXX.255。如果向全网段发送广播消息,那么广播地址为:255.255.255.255;如果向单个网段发送广播消息,例如你的IP是192.168.31.104,那么广播地址为192.168.31.255。广播消息接收方需要绑定0.0.0.0地址并监听指定端口即可收到广播的群......
  • dp泄露问题
    快速幂$$\5*e\equiv1\pmod7\e=5^{7-1-1}快速幂\a\equiva^{p}\pmod{p},assert\p\is\prime费马小定理\a*a^{p-2}\equiv1\pmod{p}$$dp泄露$$已知e,dp,c,n\ed\equiv1\pmod{phi}\dp=d\mod{p-1}\ed-1=k_1(q-1)(p-1)\d=k_......
  • 线性DP——伴随插入、删除操作
    编辑距离题目描述设\(A\)和\(B\)是两个字符串。我们要用最少的字符操作次数,将字符串\(A\)转换为字符串\(B\)。这里所说的字符操作共有三种:删除一个字符;插入一个字符;将一个字符改为另一个字符。\(A,B\)均只包含小写字母。输入格式第一行为字符串\(A\);第二行为......
  • 如何理解UDP 和 TCP? 区别? 应用场景?
    一、UDPUDP(UserDatagramProtocol),用户数据包协议,是一个简单的面向数据报的通信协议,即对应用层交下来的报文,不合并,不拆分,只是在其上面加上首部后就交给了下面的网络层也就是说无论应用层交给UDP多长的报文,它统统发送,一次发送一个报文而对接收方,接到后直接去除首部,交给上面的应......
  • Ubuntu E: 无法获得锁 /var/lib/dpkg/lock-frontend问题解决
    问题描述ubuntu18.04版本在更新出现:E:无法获得锁/var/lib/dpkg/lock-frontend-open(11:资源暂时不可用)即这个错误表明Ubuntu系统在尝试使用APT(高级包装工具)时无法获取一个锁文件。锁文件用于防止多个进程同时修改系统软件包数据库,以防止数据库损坏。错误信息中的“......
  • JAVA线程池ScheduledThreadPool实践教程
    ScheduledThreadPool用于在给定的延迟之后,或者定期执行任务。以下是如何在Java中实践使用ScheduledThreadPool的步骤:步骤1:创建ScheduledThreadPool首先,使用Executors的newScheduledThreadPool方法来创建一个ScheduledThreadPool。参数是你想要在池中保持的线程数量。i......
  • 技术支持Tektronix泰克DPO5104B数字示波器1GHz
    泰克DPO5104B数字示波器Bandwidth:1GHz4频道纪录长度:125米SampleRate:10/5GS/s(2/4ch)最多250兆跳记录长度,多视图变焦器。最大波形捕获率带310000帧的快速帧分段内存采集模式每秒捕获率标准的无源电压探针,其电容性小于4pp装载和500兆赫或1千兆赫模拟带宽......