容斥原理

https://www.cnblogs.com/wzxbeliever/p/16484848.html

这是一道非常好的容斥题目

求矩形的并集 并且可以在规定区域内求矩形的并集

https://www.luogu.com.cn/problem/P1450

分析:一道非常牛逼的容斥

如果我们就赤裸裸的多重背包肯定超时

那么怎么办呢?如果没有硬币数量的限制那就多好啊?直接一个完全背包预处理,然后O(1)输出就好了

可是有了硬币的限制怎么办?我们先考虑一个简单一点的情况:只有第一个硬币有限制。

我们先完全背包预处理好无限制的情况,拿dp[tot]减去dp[tot-c[i]*(d[i]+1)]就是我们所需的方案数

无限制的情况就是没有那个di,而有限制时,不应该计入答案的方案数就是把c[i]这个硬币取了超过d[i]次,对吧?

那么我们手动先取出d[i]+1个c[i]的硬币,然后剩下的价值弄个完全背包,这时就是我们所不需要的答案, 把它减掉就行了。

为什么是c[i](d[i]+1) 而不是 c[i]d[i]?

取c[i]*d[i]剩下的容量 也是跑的所有硬币的完全背包 可能就会有c[i]没有用到的情况 这样c[i]的个数还是d[i]个 还是合法的

所以我们取c[i]*(d[i]+1) 这样一定能保证c[i]是取超了的

这样四个硬币直接容斥一下就好了

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int maxn=1e5+5;
void solve();
int d[5],c[5],s;
ll dp[maxn],ans;
int main(){
	for(int i=1;i<=4;i++)cin>>c[i];
	int T;cin>>T;
	while(T--)solve();
     return 0;
}
void solve(){
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=4;i++)cin>>d[i];
	cin>>s;
	dp[0]=1;ans=0;
	for(int i=1;i<=4;i++)
	for(int j=0;j+c[i]<maxn;j++)
	dp[j+c[i]]+=dp[j];
	int S=1<<4;
	for(int i=0;i<S;i++){
		int p=__builtin_popcount(i);
		int res=0;
		for(int j=0;j<=3;j++)
		if((i>>j)&1)res+=(d[j+1]+1)*c[j+1];
		if(s>=res){
			if(p&1)ans-=dp[s-res];
			else ans+=dp[s-res];
		}
	}
	cout<<ans<<endl;
}

想起了之前比赛的一道题目

一个序列 给定一个最大值max 最小值min 求有多少子序列满足最大值为max 最小值为min

分析:设calc(L,R)表示满足取值范围在[L,R]的子序列个数

这个很好处理 只要线性扫过去就行

但是题目要求一定要取到最小一定是L 最大一定是R

正难则反 很容易想到容斥

只取到了L:calc(L,R-1)

只取到了R: calc(L+1,R)

两个都没取到:calc(L+1,R-1)

ANS=calc(L,R)-calc(L+1,R)-calc(L,R-1)+calc(L+1,R-1)

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 200010;
 
LL n, a[N];
LL ans;
 
void solve(LL x, LL y, LL t) {
	int lst = 1;
	for(int i = 1; i <= n; ++ i) {
    	if(a[i] > x or a[i] < y) {
    		lst = i + 1;
    	}
    	ans += (i - lst + 1) * t;
    }
}
 
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    LL x, y;
    cin >> n >> x >> y;
    for(int i = 1; i <= n; ++ i) cin >> a[i];
    solve(x, y, 1);
    solve(x - 1, y, -1);
    solve(x, y + 1, -1);
    solve(x - 1, y + 1, 1);
    cout << ans << "\n";
    return 0;
}


P3813 [FJOI2017] 矩阵填数

一道非常好的容斥题目

分析:

#include<cstdio>
#include<algorithm>
using namespace std;const int N=15;const int M=1050;typedef long long ll;const ll mod=1e9+7;
inline ll po(ll a,ll p){ll r=1;for(;p;p>>=1,a=a*a%mod){if(p&1){r=r*a%mod;}}return r;}
int n;int m;ll h;ll w;ll s[M];ll u[M];int up;int siz[M];ll res;int T;
struct retc//矩形类 
{
    ll x;ll y;ll x1;ll y1;int v;
    inline void rd(){scanf("%lld%lld%lld%lld%d",&x,&y,&x1,&y1,&v);}
    inline bool ck(){return (x>x1)||(y>y1);}//是否为空 
    inline ll calcs(){return (x1-x+1)*(y1-y+1);}//求面积 
    void operator &=(const retc& a)//交 
    {x=max(x,a.x);y=max(y,a.y);x1=min(x1,a.x1);y1=min(y1,a.y1);}
    friend bool operator <(retc a,retc b){return a.v<b.v;}
}r[N],tr;
inline void solve()
{
    scanf("%lld%lld%d%d",&h,&w,&m,&n);
    for(int i=0;i<n;i++){r[i].rd();}sort(r,r+n);up=(1<<n)-1;
    for(int i=1;i<=up;i++)//暴力求交集面积 
    {
        tr.x=1;tr.y=1;tr.x1=h;tr.y1=w;
        for(int p=i,j=0;p;p>>=1,j++){if(p&1){tr&=r[j];if(tr.ck()){s[i]=0;goto ed;}}}
        s[i]=tr.calcs();ed:;
    }
    for(int i=1;i<=up;i++)//暴力求并集面积 
    {
        for(int j=i;j;j=(j-1)&i)
        {if(siz[j]%2){u[i]+=s[j];}else {u[i]-=s[j];}}//奇数相加 偶数相减 
    }
    int ns=0;int ls=0;res=1;
    for(int i=0;i<n;i++)//分值域统计方案数 
    {
        ns|=(1<<i);if(r[i].v==r[i+1].v){continue;}
        ll tot=u[ns|ls]-u[ls];
		ll st=tot;
		ll ret=po(r[i].v,tot);
        for(int k=ns;k;k=(k-1)&ns)
        {
            tot=u[k|ls]-u[ls];
            ll del=po(r[i].v-1,tot)*po(r[i].v,st-tot)%mod;
            if(siz[k]%2){ret=(ret+mod-del)%mod;}
            else {ret=(ret+del)%mod;}
        }res=res*ret%mod;ls|=ns;ns=0;//乘起来	
    }printf("%lld\n",res*po(m,h*w-u[up])%mod); 
}
inline void clear(){for(int i=0;i<=up;i++){u[i]=0;}}
int main()
{
    for(int i=1;i<=1023;i++){siz[i]=siz[i>>1]+(i&1);}scanf("%d",&T);
    for(int z=1;z<=T;z++){solve();clear();}return 0;//拜拜程序~ 
}

P5505 [JSOI2011] 分特产

还是感觉没有把容斥搞透彻

分析:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
const int mod=1e9+7;
int c[2005][2005],n,m,a[2005];
int pow(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1)ans=ans*a%mod;
		a*a%mod;
		b>>=1;
	}
	return ans;
}
signed main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	scanf("%d",&a[i]);
	for(int i=0;i<=2000;i++)
	c[i][i]=c[i][0]=1;
	for(int i=1;i<=2000;i++)
		for(int j=1;j<=i;j++)
		c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
	int ans=0;
	for(int i=0;i<=n-1;i++)
	{
		int cnt=1;
		for(int j=1;j<=m;j++)
		cnt=cnt*c[n+a[j]-i-1][n-i-1]%mod,cnt%=mod;
		if(i%2)ans=(ans-cnt*c[n][i]%mod+mod)%mod;
		else ans=(ans+cnt*c[n][i]%mod)%mod;
	}
	printf("%d\n",ans);
	return 0;	
}

P6076 [JSOI2015] 染色问题

分析:

总结:

灵魂操作 每种颜色都至少出现一次 相当于求交集 这样用容斥即可

现在怎么处理行和列的限制

我们依次考虑每一列 要保证每一行都要有颜色 只要保证每一行 取列的情况没有全空

现在问题还要保证每列都要有颜色 所以问题又变成了求交集 这样还是用容斥

因为每一行的情况是独立的 所以我们每次强制每一行都是选的那几列

这操作真的太牛逼了

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int SIZE = 4e2 + 5;
const int mod = 1e9 + 7;

int n, m, c;
int inv[SIZE], fac[SIZE], f[SIZE];

namespace GTR {
	const int bufl = 1 << 15;
	char buf[bufl], *s = buf, *t = buf;
	inline int fetch() {
		if (s == t) { t = (s = buf) + fread(buf, 1, bufl, stdin); if (s == t) return EOF; }
		return *s++;
	}
	inline int read() {
		int a = 0, b = 1, c = fetch();
		while (c < 48 || c > 57) b ^= c == '-', c = fetch();
		while (c >= 48 && c <= 57) a = (a << 1) + (a << 3) + c - 48, c = fetch();
		return b ? a : -a;
	}
} using GTR::read;

int qPow(int a, int b) {
    int ans = 1ll;
    for ( ; b; b >>= 1, a = a * a % mod) {
        if (b & 1) ans = ans * a % mod;
    }
    return ans % mod;
}

void init() {
    const int N = 4e2;
    fac[0] = 1ll;
    for (int i = 1; i <= N; ++ i) fac[i] = fac[i - 1] * i % mod;
    inv[N] = qPow(fac[N], mod - 2ll);
    for (int i = N - 1; ~i; -- i) inv[i] = inv[i + 1] * (i + 1) % mod;
}

int C(int x, int y) {
    if (x < y) return 0ll;
    return fac[x] * inv[y] % mod * inv[x - y] % mod;
}

signed main() {
    n = read(), m = read(), c = read(); init();
    for (int i = 1; i <= c; ++ i) {
        f[i] = (qPow(i + 1, m) - 1ll + mod) % mod, f[i] = qPow(f[i], n) % mod;
        int res = 0;
        for (int k = 1; k <= m; ++ k) {
            int pw = (qPow(i + 1, m - k) - 1ll + mod) % mod; pw = qPow(pw, n) % mod;
            if ((k - 1) & 1) res = (res - (pw * C(m, k) % mod) + mod) % mod;
            else res = (res + (pw * C(m, k) % mod)) % mod;
        }
        f[i] = (f[i] - res + mod) % mod;
    }
    int ans = f[c], res = 0;
    for (int i = 1; i <= c; ++ i) {
        if ((i - 1) & 1) res = (res - (C(c, i) * f[c - i] % mod) + mod) % mod;
        else res = (res + (C(c, i) * f[c - i] % mod)) % mod;
    }
    printf("%lld\n", (ans - res + mod) % mod);
    return 0;
}

https://www.luogu.com.cn/problem/CF348D

分析:就是LGV定律的裸题目

dp遍历一遍 然后套用公式即可

CF920G 【List Of Integers】

题意:T组询问(<=30000) 求与P互质且大于X的第K大的数(P,X,K<=1e6)

互补对数

https://iai.sh.cn/problem/416

分析:

总结:按位与操作就要想到高位前缀和 !!!

#include<bits/stdc++.h>
using namespace std;
int w[5000001];
int ai[5000001];
const int mask=(1<<20)-1;
int main(){
    memset(w,0,sizeof(w));
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        int y;
        cin>>y;
        ai[i]=y;
        w[y]++;//0代表这一位0 1代表这一位1/0
    }
    for(int i=0;i<=20;i++){
        for(int j=0;j<=2000001;j++){
            if((1<<i) & j)w[j]+=w[j-(1<<i)];
        }
    }
    long long ans=0;

    for(int i=1;i<=n;i++)ans+=w[ai[i]^mask];//每对数都被计算了两次
    cout<<ans/2<<endl;
}

1.部分和(牛客网NOIP赛前集训营-普及组(第四场))部分和

输入一个长度为n的数组a[i],下标从0开始(0到n-1)
保证n是2的整数次幂,
对于每个i (0 <= i < n)
求所有满足((i & j) == j)的a[j]之和。
n<=2^20

分析:按位与 首先想到高维前缀和

列举了每一位i和j的对应情况

i : 1 1 0 0

j : 1 0 1 0

得:1 0 0 0

所以 i当前位取 1 时 满足j的当前位可以取 0 和 1

而 i当前位取 0 时 满足j当前位只能取 0

所以一个高位前缀和就好

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
void solve();
const int maxn=1<<21;
int n;
ll a[maxn];
int main(){
	int T;T=1;
	while(T--)solve();
     return 0;
}
void solve(){
	cin>>n;
	for(int i=0;i<n;i++)cin>>a[i];
	for(int i=0;i<n;i++)
	for(int j=0;j<maxn;j++)
	if(j&(1<<i))a[j]+=a[j^(1<<i)];
	for(int i=0;i<n;i++)cout<<a[i]<<endl;
}

https://codeforces.com/contest/449/problem/D

分析:

这个题目我想了很久才想明白

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define mod 1000000007
#define N 3000000
using namespace std;
inline int read(){
	int ret=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
	while(ch<='9'&&ch>='0')ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}
int n,x,dp[N],g[N];
long long f[N];
long long ans=0;
int main()
{
	scanf("%d",&n);
	int mx=(1<<20)-1;
	for(int i=1;i<=n;i++)dp[read()]++;
	f[0]=1;
	for(int i=1;i<=n;i++) f[i]=(f[i-1]<<1)%mod;
	for(int i=1;i<=20;i++)
	for(int j=0;j<=mx;j++)
	if(j&(1<<i-1)){
		dp[j^(1<<i-1)]+=dp[j];
	}
	ans=f[n]-1;
	for(int i=1;i <= mx;i++)
	{
		g[i]=g[i>>1]+(1&i);
		if(g[i]&1) ans=(ans-(f[dp[i]]-1)+mod)%mod;
		else ans=(ans+(f[dp[i]]-1)+mod)%mod;
	}
	printf("%lld\n",ans);
	return 0;
}


针对至多/至少的题目进行容斥就需要用到

**二项式反演 **

https://zhuanlan.zhihu.com/p/602675596

posted @ 2023-08-26 00:36  wzx_believer  阅读(35)  评论(0)    收藏  举报