容斥原理
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)

互补对数

分析:

总结:按位与操作就要想到高位前缀和 !!!
#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;
}
针对至多/至少的题目进行容斥就需要用到
**二项式反演 **

浙公网安备 33010602011771号