ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

容斥做题记录(早期)

2021-09-28 22:01:43  阅读:180  来源: 互联网

标签:记录 int sum 容斥 long 反演 做题 dp getchar


平邑一中集训被容斥 dp 和数位 dp 吊起来打

打算回来补补 dp


P1447 [NOI2010] 能量采集

结果是个神仙数学题

看到题一开始以为是个仪仗队

后来才发现 \(i\) 和 \(j\) 限制不同,欧拉函数不能一下切掉

看了题解之后才知道是容斥题

\[\sum_{i=1}^n\sum_{j=1}^mgcd(i,j) \]

可以考虑设 \(g[x]\) 为能够被x整除的二元组 \((i,j)\) 的个数

那么显然,$$g[x]=\left\lfloor\frac{n}{x}\right\rfloor\times\left\lfloor\frac{m}{x}\right\rfloor$$

设 \(f[x]\) 为最大公因数为 \(x\) 的二元组个数,这玩意不好求

考虑容斥

\[f[x]=g[x] - \sum_{i=2}^{\left\lfloor\frac{min(n,m)}{x}\right\rfloor} \]

然后f就变得可求了

我似乎在 day10 的 T1 里面想到过类似的东西

复杂度大概是调和级数\(O(n\log n\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define int long long
#define INF 1ll<<30
#define ill unsigned long long 

template<typename _T>
inline void read(_T &x)
{
	x=0;char s=getchar();int f=1;
	while(s<'0'||s>'9') {f=1;if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
	x*=f;
}

const int np = 1e5 + 5;
int f[np];

signed main()
{
	
	int n,m;
	read(n);
	read(m);
	
	for(int x = min(n , m);x;x--)
	{
		f[x] += (n/x) * (m/x);
		for(int i=2;x * i <= min(n , m);i++)
		f[x] -= f[i * x];
	}
	
	int Ans= 0 ;
	
	for(int i=1;i<=min(n,m);i++)
	{
		Ans += f[i] * i;
	}
	Ans*=2;
	Ans -=m * n;
	
	cout<<Ans;
}

这是个比整除分块更优的仪仗队解法


CF1528C Trees of Tranquillity

如果只有一个乌龟,则是经典的格路计数问题。

考虑一个合法的方案可能是$$(1,2)->(n-1,m),(2,1)->(n,m-1)$$

不合法的方案必定是$$(1,2)->(n,m-1),(2,1)->(n-1,m)$$

那么我们考虑一个可能合法方案的不合法情况

该情况两条路径必定有交点,考虑对最后一个交点的两端路径进行翻转,那么$$(1,2)->(n-1,m),(2,1)->(n,m-1)$$可以翻转为$$(1,2)->(n,m-1),(2,1)->(n-1,m)$$
而且翻转后与翻转前的方案一一对应

所以答案是$$solve(1,2,n-1,m)\times solve(2,1,n,m-1) - solve(1,2,n,m-1)\times solve(2,1,n-1,m)$$

#include <bits/stdc++.h>

using namespace std;

#define int long long 
const int mod = 1e9 + 7;

template<typename _T>
inline void read(_T &x)
{
	x = 0;int f= 1;char s = getchar();
	while(s<'0'||s>'9'){f=1;if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
	x*=f;
}

const int np = 3e3+ 5;

char s[np][np];
int a[np][np];
int f[np][np];
int n,m;
inline int solve(int st_x,int st_y,int end_x,int end_y)
{
	f[st_x][st_y] = 1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(a[i][j])
			{
				f[i][j] = f[i-1][j] + f[i][j-1] + f[i][j];
				f[i][j]%=mod;
			}
			else f[i][j] = 0;
		}
	}
	return f[end_x][end_y];
}

signed main()
{
	read(n);
	read(m);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s[i] + 1);
		int len = strlen(s[i] + 1);
		for(int j=1;j<=len;j++)
		a[i][j] = s[i][j]=='.'?1:0;
	}
	
	int x = solve(1,2,n-1,m);
	memset(f,0,sizeof(f));
	int y = solve(2,1,n,m-1);
	memset(f,0,sizeof(f));
	int xx = solve(1,2,n,m-1);
	memset(f,0,sizeof(f));
	int yy = solve(2,1,n-1,m);
	cout<< ((x * y - xx * yy + mod)%mod + mod) %mod;
}

有思维难度的容斥 dp


我们先展示两种科技:子集反演、二项式反演
子集反演:

\[g(S) = \sum_{T\subseteq S}f(T) \]

\[f(S)=\sum_{T\subseteq S}(-1)^{\left\vert S\right\vert - \left\vert T\right\vert}g(T) \]

二项式反演(基本:

\[f(n) = \sum_{i=0}^n(-1)^i\dbinom{n}{i}g(i)\]

\[g(n) = \sum_{i=0}^n(-1)^i\dbinom{n}{i}f(i) \]

扩展:

\[f(n) = \sum_{i=0}^n\dbinom{n}{i}g(i) \]

\[g(n) = \sum_{i=0}^n(-1)^{n-i}\dbinom{n}{i}f(i) \]

二项式反演有广义容斥证明方法,这里不写了

子集反演本质就是广义容斥,很好想,不证了

P3349 [ZJOI2016]小星星

看到 N 的范围很小,直接dp

看一个朴素的状压 dp 解法

dp[i][j][S] 表示以 i 为根的子树选 j 标号,它的子树选了 S 这个集合的标号

每次转移枚举子集即可,总复杂度 \(O(n^33^n)\)

显然过不去。

我们考虑暴力中求的是唯一对应,即 i 的子树唯一对应集合 S

优化掉枚举子集的复杂度,需要使用容斥原理,

将 dp 方程改为以 i 为根的子树选 j 标号,它的子树至多在 S 这个集合内选标号。

\[dp[u][i][S] = \prod_{v\in son(u)}\sum_{i,j\in S}dp[v][j][S] \]

每次枚举一个 S,然后在树上 dp 即可

统计答案的时候用一下前面提到的『子集反演』科技,逆向求 g

虽然提倡不要学科技,但是题解中不用科技硬找容斥系数也太奇怪了……

#include <bits/stdc++.h>

using namespace std;

#define int long long 
#define lowbit(x) (x&(-x))

const int mod = 1e9 + 7;

template<typename _T>
inline void read(_T &x)
{
	x = 0;int f= 1;char s = getchar();
	while(s<'0'||s>'9'){f=1;if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
	x*=f;
}

const int np = 19;
const int npp = (1ll << 21) + 5;

int n,m;

int head[np] , ver[np * 4] , nxt[np * 4];
int tit;
inline void add(int x,int y)
{
	ver[++tit] = y;
	nxt[tit] = head[x];
	head[x] = tit;
}

int Edge[np][np];
int f[np][np];
int g[npp];

inline void dfs(int x , int fa,int S)
{
	for(int i=1;i<=n;i++) f[x][i] = 1ll;
	for(int i=head[x] , v;i;i=nxt[i])
	{
		v = ver[i];
		if(v == fa) continue;
		dfs(v , x,S);
		
		for(int i=1;i<=n;i++)
		{
			if(!(1ll<<(i-1) & S)) continue;
			int op = 0;
			for(int j=1;j<=n;j++)
			{
				if(i == j) continue;
				if(!(1ll<<(j-1) & S)) continue;
				if(Edge[i][j]) op += f[v][j];
			}
			f[x][i] *= op ;
		}
	}
	
}

signed main()
{
	read(n);
	read(m);
	
	for(int i=1,u,v;i<=m;i++)
	{
		read(u);
		read(v);
		
		Edge[u][v] = 1;
		Edge[v][u] = 1;
	}
	
	for(int i=1,u,v;i<=n-1;i++)
	{
		read(u);
		read(v);
		add(u ,v);
		add(v ,u);
	}
	
	int f_ = 0;
	for(int i = 0; i < 1ll<<n ; i++)
	{
		dfs(1 , 0 , i);
		for(int j=1;j<=n;j++)
		g[i] += f[1][j];
		int cnt_x = 0 ;
		for(int x = i;x;x-=lowbit(x)) cnt_x++; 
		f_ += (n - cnt_x) & 1?-g[i]:g[i]; 
	}
	cout<<f_;
}

# P2167 [SDOI2009]Bill的挑战

有二项式反演做法……

但我不会,直接暴力状压 dp

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define lowbit(x) (x & (-x))
const int mod = 1000003;

template<typename _T>
inline void read(_T &x)
{
	x = 0;int f= 1;char s = getchar();
	while(s<'0'||s>'9'){f=1;if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
	x*=f;
}

const int np = 17;
const int npp = (1 << 17) + 5;

int f[np * 4][npp]; 

char c[np][np << 2];
int v[np * 4][29];

inline int Gets(char x)
{
	return x - 'a' + 1;
}

int st[2333];
int top;

inline void print(int x)
{
	while(x) st[++top] = x&1 , x>>=1;
	for(int i=1;i<=top;i++)
	{
		std::cout<<st[i];
	}
	std::cout<<'\n';
}

signed main()
{
	
	int T;
	read(T);
	while(T--)
	{
		memset(f,0,sizeof(f));
		int n,k;
		read(n);
		read(k);
		int len;
		for(int i=1;i<=n;i++)
		{
			scanf("%s",c[i] + 1);
			len = strlen(c[i] + 1);
		}
		
		for(int i=1;i<=len;i++)
		{
			for(int j=1;j<=26;j++)
			{
				int op = 0;
				for(int c_=1;c_<=n;c_++)
				{
					if(Gets(c[c_][i]) == j || c[c_][i] == '?')
					{
						op += 1 << c_ - 1;
					}
				}
				v[i][j]  = op;
			}		
		}
		
		f[0][(1<<n) - 1] = 1;
		
		for(int i=1;i <= len;i++)
		{
			for(int ch=1;ch<=26;ch++)
			{
				for(int T = 0 ; T < 1<<n;T++)
				{
					f[i][T & v[i][ch]] += f[i - 1][T];	
					f[i][T & v[i][ch]] %= mod;
					
				}
			}
		}
		
		int Ans =0 ;
		for(int i=0;i < 1<<n ; i++)
		{
			int cnt_ = 0;
			for(int u = i;u ; u -= lowbit(u)) cnt_++;
			if(cnt_ == k) Ans += f[len][i] , Ans %= mod;
		}
		cout<<Ans<<'\n';		
	}
	
}

标签:记录,int,sum,容斥,long,反演,做题,dp,getchar
来源: https://www.cnblogs.com/-Iris-/p/15350287.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有