1. 程式人生 > 其它 >容斥做題記錄(早期)

容斥做題記錄(早期)

平邑一中集訓被容斥 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';		
	}
	
}