• 容斥做题记录(早期)


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

    打算回来补补 dp


    P1447 [NOI2010] 能量采集

    结果是个神仙数学题

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

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

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

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

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

    那么显然,$$g[x]=leftlfloorfrac{n}{x} ight floor imesleftlfloorfrac{m}{x} ight floor$$

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

    考虑容斥

    [f[x]=g[x] - sum_{i=2}^{leftlfloorfrac{min(n,m)}{x} ight floor} ]

    然后f就变得可求了

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

    复杂度大概是调和级数(O(nlog nlog 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) imes solve(2,1,n,m-1) - solve(1,2,n,m-1) imes 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_{Tsubseteq S}f(T) ]

    [f(S)=sum_{Tsubseteq S}(-1)^{leftvert S ightvert - leftvert T ightvert}g(T) ]

    二项式反演(基本:

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

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

    扩展:

    [f(n) = sum_{i=0}^ndbinom{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_{vin son(u)}sum_{i,jin 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<<'
    ';
    }
    
    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<<'
    ';		
    	}
    	
    }
    
    
  • 相关阅读:
    EasyARM-Linux工具
    EasyARM-Linux文件系统
    EasyARM-Linux使用
    公差-PCBA
    novoton-USBDevice使用
    novoton-RTC使用
    novoton-ADC使用
    novoton-I2C使用
    novoton-timer使用
    novoton-usart使用
  • 原文地址:https://www.cnblogs.com/-Iris-/p/15350287.html
Copyright © 2020-2023  润新知