又叫抽屉原理
最基本的描述:如果有n种n+1个物品,那么至少有一种有两个物品
这种基本概念也没什么太多好阐释的,主要是鸽笼定理在思维上可能会造成一些奇怪的突破口
直接上题吧
四倍经验题了解一下
题意:给你两个整数C和N,再给你N个正数,从中找到若干数(不要求连续),使得其和刚好是 C的倍数。c<=n<=100000
我们先让这些数对C取模,然后求前缀和
那么总共最多会有C种前缀和,但是实际上我们只需要考虑C-1中,因为前缀和等于0是本身就是答案
然后根据抽屉原理,由于 C−1<N C − 1 < N ,所以一定有两个前缀和相同
所以呢尽管题目说了不要求连续,但是我们可以保证存在连续的满足答案
//其实POJ2356略有不同,不过差别不大
#include
#include
#include
using namespace std;
const int N=1e5+5;
int n,m;
int a[N];
int s[N];
int vis[N];
int main()
{
while(~scanf("%d%d",&m,&n)&&m){
memset(vis,0,sizeof vis);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
a[i]%=m;
s[i]=(a[i]+s[i-1])%m;
}
for(int i=1;i<=n;i++){
if(s[i]==0){
for(int j=1;j<=i;j++)
printf("%d ",j);
puts("");
break;
}
if(vis[s[i]]){
for(int j=vis[s[i]];j<=i;j++)
printf("%d ",j);
puts("");
break;
}
vis[s[i]]=i+1;
}
}
}
POJ 3844
题意基本一样,给你两个整数M和N,再给你N个正数,从中找到连续的若干数,使得其和刚好是 M的倍数的方案数。
额…本质上也是一样的,比如%M=i出现了cnti次,那么答案加上C(cnti,2)
题意:多组数据(<=50) 有N个点,横纵坐标都在[0,M]中,然后问你能否找到两对点使得这两对点的曼哈顿距离相同 N,M<=1e5
由于总共就只有2e5种曼哈顿距离的取值,那么如果我们遍历了2e5+1个点对,就一定会出现重复,所以我们直接暴力
#include
#include
#include
using namespace std;
const int N=2e5+5;
int n,m;
bool vis[N];
int x[N],y[N];
void Solve(){
memset(vis,0,sizeof vis);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++){
int d=abs(x[i]-x[j])+abs(y[i]-y[j]);
if(vis[d]){
puts("YES");
return ;
}
vis[d]=1;
}
puts("NO");
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d%d",&x[i],&y[i]);
Solve();
}
}
题意:一个飞镖盘分为n个区域(n<=100),你投中一个区域会得到wi的分(1<=wi<=100),然后问你使得总分恰好为m(1<=m<=1e9)最少要投多少次飞镖(无解输出-1)
我们先把w数组排序
我们可以把它们每次得到的分作为一串数字,那么类似于例题中我们所用的方法,我们可以证明,当这串数字的长度大于等于wn时,一定存在一个子串的和是wn的倍数。那么也就是说价值小于wn的我们最多只会使用wn次(更准确的说是wn-1次)
然后我们可以先用剩下的n-1个进行完全背包,背包容量是wn*wn,然后对于每一个状态计算若要填满m,wn的使用次数即可
更简便一点的做法是我们把这n个都进行完全背包,然后我们求出最大的与m的差是wn的倍数的小于wn*wn的位置即可
#include
#include
#include
using namespace std;
const int N=10005;
int n;
int d[N];
int w[N];
int main()
{
int t;
scanf("%d",&t);
while(t--){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
sort(w+1,w+n+1);
int mx=w[n]*w[n];
memset(d,-1,sizeof d);
d[0]=0;
for(int i=1;i<=n;i++)
for(int j=w[i];j<=mx;j++)
if(d[j-w[i]]!=-1&&(d[j]==-1||d[j]>d[j-w[i]]+1))
d[j]=d[j-w[i]]+1;
int tot=m,cnt=0;
if(m>mx){
tot=m%mx;
cnt=(m-tot)/w[n];
}
int ans;
if(d[tot]==-1)
ans=-1;
else
ans=cnt+d[tot];
printf("%d\n",ans);
}
}
题意:有N(n<=1e5)个数整(1<=ai<=1000),q组询问(q<=10000),每次询问[l,r]这个区间内所有的数对中最小的差值是多少
乍一看是不是什么数据结构神题
由于ai<=1000,那么也就是说
if(r-l+1>1000)
puts("0");
然后直接1000*q的大暴力即可
#include
#include
#include
using namespace std;
const int N=100005;
int a[N];
int z[N];
int main()
{
int T;
scanf("%d",&T);
for(int t=1;t<=T;t++){
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i"%d",&a[i]);
printf("Case %d:\n",t);
for(int i=1;i<=m;i++){
int l,r;
scanf("%d%d",&l,&r);
if(r-l+1>1000)
puts("0");
else{
for(int i=l;i<=r;i++)
z[a[i]]++;
int ans=1000;
int last=-1;
for(int i=1;i<=1000;i++){
if(z[i]>=2)
ans=0;
if(z[i]>=1){
if(last!=-1)
ans=min(ans,i-last);
last=i;
}
}
printf("%d\n",ans);
for(int i=l;i<=r;i++)
z[a[i]]--;
}
}
}
}
好了,鸽笼定理我们暂时告一段落
简单地说,如果说一些元素具有一些性质,那么这些的并集=包含至少1个性质-包含2个+包含3个…
解题的关键一般是这所谓的性质是什么性质,还有快速计算满足这一条件的集合大小的方法。而且经常使用求补集的计算技巧
(推荐一篇博客)
容斥原理在OI中的实现一般有三种(都是2^n的数量级)
1.dfs,简洁明了吧,枚举01状态计算
2.队列数组,其实类似于bfs之于dfs,就是用类似广搜的方法进行拓展恰好n层
3.位运算,强烈推荐lowbit
算了单纯的知识内容我们就跳过吧,我们直接上例题吧
题意:给出M个非负整数,问[1,n)中,有多少个数能至少被那M个数中至少一个数整除。 0<N<231,0<M<=10 0 < N < 2 31 , 0 < M <= 10
em
版题啊
我就分别用dfs和队列数组实现(位运算实现见例题4)
#include
#include
#include
using namespace std;
typedef long long ll;
const int N=15,M=1<<12;
int gcd(int x,int y){
if(!y)
return x;
return gcd(y,x%y);
}
int lcm(int x,int y){
return x/gcd(x,y)*y;
}
ll n;
int m;
int z[N],k;
ll ans;
void dfs(int x,int f,ll sum){
if(x==k){
ans+=n/sum*f;
return ;
}
dfs(x+1,f,sum);
dfs(x+1,-f,lcm(sum,z[x]));
}
int q[M];
int bfs(){
int t=0;
q[0]=-1;
ll ans=0;
for(int i=0;iint cnt=t;
for(int j=0;j<=cnt;j++)
q[++t]=q[j]/gcd(abs(q[j]),z[i])*z[i]*(-1);
}
for(int i=1;i<=t;i++)
ans+=n/q[i];
return ans;
}
int main()
{
while(~scanf("%I64d%d",&n,&m)){
n--;//因为是闭区间
k=0;
for(int i=1;i<=m;i++){
int x;
scanf("%d",&x);
if(x)//注意是给出非负整数
z[k++]=x;
}
ans=n;
dfs(0,-1,1);
if(bfs()!=ans)
puts("What the ****! ");
printf("%I64d\n",ans);
}
}
题意:求[a,b]区间中与n互质的数的个数
0<=a<=b<=1e15,1<=n<=1e9
我们先考虑求[1,m]内与n互质的数
所以我们先质因数分解,然后就转化成了例题3,然后容斥原理解决此题
#include
#include
#include
using namespace std;
typedef long long ll;
const int N=10005,M=1<<11;
int p[N+5],pcnt;
bool np[N+5];
int z[N+5],zcnt;
int bits[M+5];
void Init(){
for(int i=0;i<11;i++)
bits[1<for(int i=2;i<=N;i++){
if(!np[i]){
p[++pcnt]=i;
}
for(int j=1;p[j]*i<=N;j++){
np[p[j]*i]=1;
if(i%p[j]==0)
break;
}
}
}
inline int lowbit(int x){
return x&(-x);
}
ll get(ll x){
ll ans=0;
for(int i=0;i<(1<int sign=1;
ll sum=1;
int S=i,Z;
while(S){
Z=lowbit(S);
S^=Z;
Z=bits[Z];
sum*=z[Z];
sign*=-1;
}
ans+=x/sum*sign;
}
return ans;
}
int main()
{
Init();
int T;
scanf("%d",&T);
for(int t=1;t<=T;t++){
ll a,b;
int n;
scanf("%I64d%I64d%d",&a,&b,&n);
zcnt=0;
for(int i=1;i<=pcnt&&p[i]<=n;i++)
if(n%p[i]==0){
z[zcnt++]=p[i];
while(n%p[i]==0)
n/=p[i];
}
if(n!=1){
z[zcnt++]=n;
}
ll ans=get(b)-get(a-1);
printf("Case #%d: %I64d\n",t,ans);
}
}
硬币购物一共有4种硬币。面值分别为c1,c2,c3,c4。某人去商店买东西,去了tot次。每次带di枚ci硬币,买si的价值的东西。请问每次有多少种付款方法。
di,si<=100000 tot<=1000
假如我们不考虑硬币数量限制,那么答案可以通过一个简单的类似完全背包的dp求出f[i]数组表示凑出i的方案数
然后呢,这些方案和什么性质有关呢?
和不满足多少种限制有关
所以呢,答案就是总方案-至少超过1种+至少超过2种-至少超过3种+至少超过4种
那么怎么快速求出方案呢
如果我们要不满足限制,我们先要令这个硬币使用di+1枚,然后剩下的我们就任意取——也就是说,f[tot-(di+1)*si]
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N=100005,M=1000000;
const int mod=1e9+7;
ll d[N];
int v[5];
int num[5];
int n;
ll ans;
void dp(){
d[0]=1;
for(int i=1;i<=4;i++)
for(int j=v[i];j<=N-5;j++)
d[j]+=d[j-v[i]];
}
void dfs(int x,int f,int sum){
if(sum<0)
return ;
if(x==5){
ans+=1ll*f*d[sum];
return ;
}
dfs(x+1,f,sum);
dfs(x+1,-f,sum-(num[x]+1)*v[x]);
}
int main()
{
for(int i=1;i<=4;i++)
scanf("%d",&v[i]);
dp();
int m,sum;
scanf("%d",&m);
while(m--){
ans=0;
for(int i=1;i<=4;i++)
scanf("%d",&num[i]);
scanf("%d",&sum);
dfs(1,1,sum);
printf("%lld\n",ans);
}
}
还有一道题Codeforces 451E Devu and Flowers
有N种颜色的花N<=20,每种花有ai种,问你选择s朵花的方案数
其实和这道题本质上是一样的
给一串数字,求解满足四个数字gcd=1的四元组的个数
我们考虑一下这四个数的公因数的质因子个数
我们奇数个素因子-,偶数个素因子+
是不是有点像 μ μ ?嗯不过不是…
但是这道题确实有莫比乌斯反演的做法
这样我们容斥一下就可以得到答案了
#include
#include
#include
using namespace std;
typedef long long ll;
const int N=10005;
int p[N+5],pcnt;
bool np[N+5];
int sign[N+5],cnt[N+5];
int z[N+5],zcnt;
int bits[N+5];
int lowbit(int x){return x&-x;}
void Init(){
for(int i=1;i<=10;i++)
bits[1<for(int i=2;i<=N;i++){
if(!np[i]){
p[++pcnt]=i;
}
for(int j=1;p[j]*i<=N;j++){
np[p[j]*i]=1;
if(i%p[j]==0)
break;
}
}
}
void Solve(int n){
zcnt=0;
for(int i=1;i<=pcnt&&p[i]<=n;i++)
if(n%p[i]==0){
z[zcnt++]=p[i];
while(n%p[i]==0)
n/=p[i];
}
if(n>1){
z[zcnt++]=n;
}
for(int S=0;S<(1<int t=1;
int f=1;
int T=S;
while(T){
int U=lowbit(T);
T^=U;
int u=bits[U];
f=-f;
t*=z[u];
}
cnt[t]++;
sign[t]=f;
}
}
ll calc(int x){
return 1ll*x*(x-1)*(x-2)*(x-3)/24;
}
int main()
{
int n,m;
Init();
while(~scanf("%d",&n)){
memset(cnt,0,sizeof cnt);
for(int i=1;i<=n;i++){
scanf("%d",&m);
Solve(m);
}
ll ans=0;
for(int i=0;i<=N;i++)
if(cnt[i])
ans+=calc(cnt[i])*sign[i];
printf("%lld\n",ans);
}
}
题意:我们知道,有N个元素的集合有 2N 2 N 个子集(含空集),现在我们从中取出若干个子集(至少一个),使得它们的交集的元素个数为K,求取法的方案数.答案对1000000007取模
定义F(i)为交集至少为i个的选择方案
先C(n,i)从中选出i个元素,那么不含这i个元素的集合共有 2n−i 2 n − i 个
然后每一个选和不选,就有 22n−i 2 2 n − i 种方案,但是我们不能一个都不选,所以最终
F(i)=C(n,i)(22n−i−1) F ( i ) = C ( n , i ) ( 2 2 n − i − 1 )
然后容斥即可
即
#include
#include
#include
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int M=1000010;
ll ksm(ll x,ll y){
ll res=1;
while(y){
if(y&1)
res=(res*x)%mod;
x=(x*x)%mod;
y>>=1;
}
return res;
}
ll fact[M+5];
ll inv[M+5];
void pre(){
fact[0]=1;
for(int i=1;i<=M;i++)
fact[i]=(fact[i-1]*i)%mod;
inv[M]=ksm(fact[M],mod-2);
for(int i=M-1;i>=0;i--)
inv[i]=(inv[i+1]*(i+1))%mod;
}
inline ll C(ll n,ll m){
if(n<m)
return 0;
return fact[n]*inv[m]%mod*inv[n-m]%mod;
}
ll solve(ll n){
ll temp=2,ans=0;
for(int i=n;i>=0;i--){
if(i&1)
ans=(ans-(C(n,i)*(temp-1))%mod+mod)%mod;
else
ans=(ans+(C(n,i)*(temp-1))%mod)%mod;
temp=(temp*temp)%mod;
}
return ans;
}
int main()
{
//freopen("set.in","r",stdin);
//freopen("set.out","w",stdout);
pre();
ll n,m;
scanf("%lld%lld",&n,&m);
ll ans=(solve(n-m)+mod)%mod;
ans=ans*C(n,m)%mod;
printf("%lld\n",ans);
}
题意:有n个整数,问从他们中取出若干个数字相与之后结果是0的有多少组。
答案可能比较大,输出对于 1e9+7取模后的结果。
ai<220 a i < 2 20
我们先考虑这样一个问题,令f(x)=有多少个i满足a[i]&x=x
那么,答案就是
#include
#include
#include
using namespace std;
const int N=25,M=1<<20;
const int mod=1e9+7;
int n;
int d[N][M];
int pow2[M];
int main()
{
pow2[0]=1;
for(int i=1;i1ll*pow2[i-1]*2)%mod;
while(~scanf("%d",&n)){
memset(d,0,sizeof d);
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
if(x&1)
d[0][x]++,d[0][x^1]++;
else
d[0][x]++;
}
for(int i=1;i<20;i++)
for(int j=0;j<1<<20;j++)
if(j&(1<1][j];
else
d[i][j]=d[i-1][j]+d[i-1][j^(1<int ans=0;
for(int i=0;i<1<<20;i++){
if(__builtin_popcount(i)&1)
ans-=pow2[d[19][i]]-1;
else
ans+=pow2[d[19][i]]-1;
ans=(ans%mod+mod)%mod;
}
printf("%d\n",ans);
}
}
以下题目感谢yhn的博客
有 2N 2 N 个选手参与一场比赛,比赛规则是:相邻的两个人比赛一次,败者淘汰掉,胜者继续进行,直到只剩一个人为止。
现在给出1号选手会败给哪些选手
并且已知其他选手之间均满足:两个选手比赛,编号小的一定会胜利。
现在可以安排每个选手初始的位置,要钦定1号选手吃鸡最后获胜,求能满足条件的初始位置的方案数。
就放个链接了吧,下同
https://blog.csdn.net/qq_34454069/article/details/79734314
给定一棵点数为偶数的树,要求有多少种将点两两配对(配成n/2对)的方案使得每一条边至少被一对匹配点之间的路径覆盖。
https://blog.csdn.net/qq_34454069/article/details/82086374
有N个K个面的骰子,当i=2,3,4……2*k时, 求所有骰子的点数中,没有任何两个之和为i的方案数。这N个骰子不互相区分
https://blog.csdn.net/qq_34454069/article/details/82292107