参考大佬博客:https://www.cnblogs.com/linyujun/p/5210410.html
这道应该第一反应是排错问题,可以用排错问题的公式。但是,也可以用容斥原理来想。
总的方案数为n!。
假设一定有1封信(指定的,非任意)放对,则有 ( n − 1 ) ! (n-1)! (n−1)!种方案。
假设一定有2封信(指定的,非任意)放对,则有 ( n − 2 ) ! (n-2)! (n−2)!种方案。
假设一定有3封信(指定的,非任意)放对,则有 ( n − 3 ) ! (n-3)! (n−3)!种方案。
……
假设一定有n封信放对,则有 0 ! 0! 0!种方案。
之后,我们给每一项乘上组合数,就可以变成任意的1、2、……、n封信。这时候,会想到全部加起来就是不合理方案数了,但是,这里面其实有重复,所以我们需要去重。
参考wiki:https://oi-wiki.org/math/inclusion-exclusion-principle/
C n 1 ( n − 1 ) ! C^1_n(n-1)! Cn1(n−1)!可以理解为上面wiki中的|A|+|B|+|C|……,也就是A信件放对、B信件放对、C信件放对……的情况总和,但是再A信件放对的情况中,也含有B信件放对的情况。所以,我们需要减去的是A和B信件都放对的情况,也就是(n-2)!,相同的其他的C、D等的和A组合,以及他们之间的组合都要算进去,因此就变成了要 − C n 2 ( n − 2 ) ! -C^2_n(n-2)! −Cn2(n−2)!,但是这减去的里面,又有A、B、C放对的情况被多减去了一次,因此,需要 + C n 3 ( n − 3 ) ! +C^3_n(n-3)! +Cn3(n−3)!补上,然后又重复了Orz……一直套娃到最后,结果不符合题意的结果就是:
∑ 1 n ( − 1 ) n − 1 C n i ( n − i ) ! \sum_1^n(-1)^{n-1}C_n^i(n-i)! ∑1n(−1)n−1Cni(n−i)!
容斥一下,结果就是 n ! − ∑ 1 n ( − 1 ) n − 1 C n i ( n − i ) ! n!-\sum_1^n(-1)^{n-1}C_n^i(n-i)! n!−∑1n(−1)n−1Cni(n−i)!
编程实现就打表套就是了。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define rep(i, a, n) for(register int i = a; i <= n; ++ i)
#define per(i, a, n) for(register int i = n; i >= a; -- i)
#define ONLINE_JUDGE
using namespace std;
typedef long long ll;
const int mod=1e9+7;
template<typename T>void write(T x)
{
if(x<0)
{
putchar('-');
x=-x;
}
if(x>9)
{
write(x/10);
}
putchar(x%10+'0');
}
template<typename T> void read(T &x)
{
x = 0;char ch = getchar();ll f = 1;
while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;};
ll ksm(ll a,ll n){//看是否要mod
ll ans=1;
while(n){
if(n&1) ans=(ans*a)%mod;
a=a*a%mod;
n>>=1;
}
return ans%mod;
}
//==============================================================
ll fac[25];
void init(){
fac[0]=1;
rep(i,1,20){
fac[i]=fac[i-1]*i*1LL;
}
}
int n;
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
//===========================================================
init();
while (~scanf("%d",&n))
{
ll ans=0;
int sig=1;
rep(i,2,n){
ans+=sig*(fac[n]/fac[i]);
sig*=-1;
}
write(ans),putchar('\n');
}
//===========================================================
return 0;
}
从m种颜色挑k种,组合数 C m k C^k_m Cmk没毛病。由于一定要用上k种颜色,直接求是不行的,考虑容斥。若不考虑全部用上,则有 C n k k ∗ ( k − 1 ) n − 1 C^k_nk*(k-1)^{n-1} Cnkk∗(k−1)n−1种方案,需要去除不合理方案。这里不合理的方案是只使用了k-1、k-2、……、1种颜色,若只使用了其中的k-1种,则方案数为 C k k − 1 ∗ ( k − 1 ) ∗ ( k − 2 ) n − 1 C_k^{k-1}*(k-1)*(k-2)^{n-1} Ckk−1∗(k−1)∗(k−2)n−1,和上面一样,有多减去的,所以,需要补上,套娃,……最后结果为:
C m k ∗ ( k ∗ ( k − 1 ) n − 1 − ∑ i = 1 k ( − 1 ) i C k i ( k − i ) ∗ ( k − i − 1 ) n − 1 ) C^k_m*(k*(k-1)^{n-1}-\sum_{i=1}^k(-1)^iC_k^i(k-i)*(k-i-1)^{n-1}) Cmk∗(k∗(k−1)n−1−∑i=1k(−1)iCki(k−i)∗(k−i−1)n−1),由于m的范围较大,所以只能直接算。而内层的 C k i C_k^i Cki可以打表解决。
注意:因为有减法,所以需要+mod之后再%mod
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define rep(i, a, n) for(register ll i = a; i <= n; ++ i)
#define per(i, a, n) for(register ll i = n; i >= a; -- i)
#define ONLINE_JUDGE
using namespace std;
typedef long long ll;
const int mod=1e9+7;
template<typename T>void write(T x)
{
if(x<0)
{
putchar('-');
x=-x;
}
if(x>9)
{
write(x/10);
}
putchar(x%10+'0');
}
template<typename T> void read(T &x)
{
x = 0;char ch = getchar();ll f = 1;
while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;};
ll ksm(ll a,ll n){//看是否要mod
ll ans=1;
while(n){
if(n&1) ans=(ans*a)%mod;
a=a*a%mod;
n>>=1;
}
return ans%mod;
}
//==============================================================
#define int ll
ll t;
const int maxn=1e6+10;
ll fac[maxn];
ll finv[maxn];
void init(){//阶乘和阶乘逆元打表
int lim=1e6+5;
fac[0]=1;
rep(i,1,lim){
fac[i]=(fac[i-1]*i)%mod;
}
finv[lim]=ksm(fac[lim],mod-2)%mod;
per(i,0,lim-1){
finv[i]=((finv[i+1]%mod)*((i+1)%mod))%mod;
}
}
ll n,m,k;
inline ll C(ll n,ll m){//O(m)求大范围组合数
ll t1=1;
rep(i,n-m+1,n){
t1=(t1*i)%mod;
}
return ((t1%mod)*(finv[m]%mod))%mod;
}
inline ll c(ll n,ll m){//O(1)求小范围组合数
if(m<0||m>n) return 0;
return ((fac[n]*finv[n-m])%mod*finv[m])%mod;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
//===========================================================
read(t);
ll kase=0;
init();
while(t--){
read(n),read(m),read(k);
ll ans=k*ksm(k-1,n-1)%mod;
ll sig=-1;
rep(i,1,k-1){
//cout<
ans=(ans+sig*(c(k,k-i)*(k-i)%mod*(ksm(k-i-1,n-1)%mod)%mod)%mod+mod)%mod;//代式子
sig=-sig;
}
ans=(C(m,k)%mod*ans+mod)%mod;
printf("Case #%d: %lld\n",++kase,(ans%mod));
}
//===========================================================
return 0;
}
这道题涉及到素数分解和容斥原理的实现。
听说容斥原理的实现分为dfs、队列数组和二进制,这我还不是很了解。这里就用了队列数组。
原题目是求[a,b]范围内和n互质的数的数目,我们可以考虑容斥,求不和n互质的。如果直接对[a,b]范围是比较麻烦的,所以考虑用前缀和思想:[1,a-1]范围和[1,b]范围,然后两个一减就好了。
对于一个数x,求[1,x]范围内和n不互质的数:
可以把n进行素数分解为p1、p2、……、pn,然后,求再[1,x]范围内,p1、p2、……、pn的倍数的个数。因为起点是1,所以,数目就是x/p1、x/p2、x/p3、……、x/pn。和上面一样,减去x/p1和x/p2的时候会有重复,所以,还是套娃Orz。关于+和-的处理,可以采用上面说到的队列数组。具体看代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define rep(i, a, n) for(register ll i = a; i <= n; ++ i)
#define per(i, a, n) for(register ll i = n; i >= a; -- i)
#define ONLINE_JUDGE
using namespace std;
typedef long long ll;
const int mod=1e9+7;
template<typename T>void write(T x)
{
if(x<0)
{
putchar('-');
x=-x;
}
if(x>9)
{
write(x/10);
}
putchar(x%10+'0');
}
template<typename T> void read(T &x)
{
x = 0;char ch = getchar();ll f = 1;
while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;};
ll ksm(ll a,ll n){//看是否要mod
ll ans=1;
while(n){
if(n&1) ans=(ans*a)%mod;
a=a*a%mod;
n>>=1;
}
return ans%mod;
}
//==============================================================
const int maxn=1e6+10;
ll a,b,n,t;
ll primes[maxn],que[maxn];
ll front,tot;
void init(){
memset(primes,0,sizeof(primes));
memset(que,0,sizeof(que));
front=0,tot=0;
for(int i=2;i*i<=n;++i){//对n素数分解
if(n%i==0){
primes[tot++]=i;
while(n%i==0) n/=i;
}
}
if(n!=1) primes[tot++]=n;//这步不要忘记
que[front++]=-1;
rep(i,0,tot-1){//队列数组建立
ll k=front;
for(ll j=0;j<k;++j){
que[front++]=que[j]*primes[i]*(-1);
}
}
}
ll solve(){//求不和n互质的,容斥原理
ll res1=0,res2=0;
rep(i,1,front-1){
res1+=(a-1)/que[i];
}
rep(i,1,front-1){
res2+=b/que[i];
}
//cerr<
return res2-res1;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
//===========================================================
read(t);
ll kase=0;
while(t--){
read(a),read(b),read(n);
init();//对n进行素数分解。
printf("Case #%lld: %lld\n",++kase,(b-a+1)-solve());//容斥求对立事件
}
//===========================================================
return 0;
}
听说这道题的正解的莫比乌斯反演,但容斥也能做,不过估计是数据水卡过去的。
原题是:再[1,b]和[1,d]区间内,分别找两个数x,y,使得gcd(x,y)=k,问一共有多少对x、y满足条件。根据gcd形式:
gcd(x,y)=k,则gcd(x/k,y/k)=1,证明可以素数分解看下。
然后,问题就变成了再[1,b/k]和[1,d/k]范围内分别找两个数p,q,使得p、q互质。
根据上一道题的经验,直接求素数比较难,所以,求个对立事件,不互质的数。
这里我们先假设d/k>b/k,题目给的不是就swap一下。
枚举x,y两个肯定不行,但我们可以枚举其中一个,枚举y。对于y,我们还需要分类讨论:
若y<=b/k,就是我们选定y为一个定值 y i y_i yi,那么,我们要求和几个x(的范围为[1,b/k])和他互质,其实就相当于求在<= y i y_i yi范围内有几个数和yi互质(为什么不考虑 [ y i , b / k ] [y_i,b/k] [yi,b/k]范围的呢?是为了避免等会再枚举 y j y_j yj( y i y_i yi< y j y_j yj<=d/k)的时候和现在的重复)。可以用欧拉函数求前缀和。
若y>b/k,我们换个角度看问题:就是假设现在y= y k y_k yk( y k > b / k y_k>b/k yk>b/k),求在b/k范围内有几个数和它互质,就和第三题一样了。
这里有个小优化,欧拉函数需要打素数表,所以,我们在分解素数的时候可以利用素数表来分解。
另外,注意特判。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define int ll
#define inf 0x3f3f3f3f
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define rep(i, a, n) for(register ll i = a; i <= n; ++ i)
#define per(i, a, n) for(register ll i = n; i >= a; -- i)
#define ONLINE_JUDGE
using namespace std;
typedef long long ll;
const int mod=1e9+7;
template<typename T>void write(T x)
{
if(x<0)
{
putchar('-');
x=-x;
}
if(x>9)
{
write(x/10);
}
putchar(x%10+'0');
}
template<typename T> void read(T &x)
{
x = 0;char ch = getchar();ll f = 1;
while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;};
ll ksm(ll a,ll n){//看是否要mod
ll ans=1;
while(n){
if(n&1) ans=(ans*a)%mod;
a=a*a%mod;
n>>=1;
}
return ans%mod;
}
//==============================================================
const int maxn=1e5+100;
vector<ll> factor;
ll prime[maxn],tot;
ll vis[maxn];
ll eular[maxn];
ll t;
ll a,b,c,d,k;
inline void init(){//欧拉函数加前缀和
eular[1]=1;
for(ll i=2;i<maxn;i++){
if(!vis[i]) prime[tot++]=i,eular[i]=i-1;
for(int j=0;j<tot&&prime[j]*i<maxn;j++){
vis[i*prime[j]]=1;
if(i%prime[j]==0){
eular[i*prime[j]]=eular[i]*prime[j];
break;
}
else{
eular[i*prime[j]]=eular[i]*(prime[j]-1);
}
}
}
rep(i,1,maxn-1){
eular[i]+=eular[i-1];
}
}
ll que[maxn],front;
void predeal(ll n){//分解素数,创建队列数组
factor.clear();
//memset(que,0,sizeof(que));
front=0;
for(int i=0;prime[i]<=n/prime[i];++i){
if(n%prime[i]==0){
factor.push_back(prime[i]);
while(n%prime[i]==0) n/=prime[i];
}
}
if(n!=1) factor.push_back(n);
que[front++]=-1;
rep(i,0,factor.size()-1){
ll k=front;
for(ll j=0;j<k;++j){
que[front++]=que[j]*factor[i]*(-1);
}
}
}
inline ll solve(ll b,ll x){//求不互质的数
predeal(x);
ll res=0;
rep(i,1,front-1){
res+=b/que[i];
}
//cerr<
return b-res;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
//===========================================================
read(t);
int kase=0;
init();
while(t--){
read(a),read(b),read(c),read(d),read(k);
if(!k||b<k||d<k){
printf("Case %d: %lld\n",++kase,0);
continue;
}
b/=k,d/=k;
if(d<b) swap(b,d);
ll ans=eular[b];
//cerr<
rep(i,b+1,d){
ans+=solve(b,i);
}
printf("Case %d: %lld\n",++kase,ans);
}
//===========================================================
return 0;
}