zoukankan      html  css  js  c++  java
  • 容斥做题记录(早期)

    平邑一中集训被容斥 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<<'
    ';		
    	}
    	
    }
    
    
  • 相关阅读:
    hihoCoder #1062 : 最近公共祖先·一
    hihoCoder #1050 : 树中的最长路
    hihoCoder #1049 : 后序遍历
    108 Convert Sorted Array to Binary Search Tree 将有序数组转换为二叉搜索树
    107 Binary Tree Level Order Traversal II 二叉树的层次遍历 II
    106 Construct Binary Tree from Inorder and Postorder Traversal 从中序与后序遍历序列构造二叉树
    105 Construct Binary Tree from Preorder and Inorder Traversal 从前序与中序遍历序列构造二叉树
    104 Maximum Depth of Binary Tree 二叉树的最大深度
    102 Binary Tree Level Order Traversal 二叉树的层次遍历
    101 Symmetric Tree 判断一颗二叉树是否是镜像二叉树
  • 原文地址:https://www.cnblogs.com/-Iris-/p/15350287.html
Copyright © 2011-2022 走看看