组合数学(上):数列、排列、组合

数列

取得整型数据的位数

cpp 复制代码
#define LL long long
int NoD(LL x){//Number of Digits
    int sum=0;
    if(x%10==0) sum++;
    while(x>0)
    {x/=10;sum++;}
    return sum;
}

整型数据各数位求和

c 复制代码
int SoD(int x){//Sum of the Digits
	int y = 0;
	while(x > 0) { y += x % 10; x /= 10; }
	return y;
}

反转数(Reversed number)

c 复制代码
#define LL long long

LL Reverse(LL x){
	LL ans=0;
	while(x) 
	{ans=ans*10+x%10;x/=10;}
    return ans;
}

前缀和

设sum[i]为数列中前i项的前缀和,s[i]为数列中第i项的值,如果sum[a]与sum[b]对M同余(a<b),则
( ∑ k = a + 1 b s k ) % M = = 0 (\sum_{k=a+1}^{b}s_k) \% M == 0 (k=a+1∑bsk)%M==0

👉HDOJ-1003 Max Sum

题目:给出一个数列,找出其中各项之和最大的子数列,输出其起点位置、终点位置与各项之和。

思路:如果数列全是负项,最大和即为最大项的值。否则,最大和一定非负。在输入时进行检查,初始化首项为子数列起点。先更新最大值,再判断和符号,如果为负,从下一项开始新的子数列。证明:能够判断出总和为负,说明最新项为负,此前和已取到极大值,虽然之前的项包含之后的项有可能取到更大的和,但是之后的项不包含之前的项和一定更大,所以之前的项与之后的项应以最新项作为分界点分开。

c 复制代码
#include<stdio.h>
int main(){
    int t;
    scanf("%d",&t);
    for(int j=1;j<=t;j++){
        int n,i,ms,b,mb,s,temp,e;
        scanf("%d",&n);
        ms=-1001;//和的最小值为-1000
        mb=b=1;s=0;
        for(i=0;i<n;i++){
            scanf("%d",&temp);
            s+=temp;
            if(s>ms){
                ms=s;
                mb=b;
                e=i+1;
            }
            if(s<0){
                b=i+2;
                s=0;
            }
        }        
        printf("Case %d:\n",j);        
        printf("%d %d %d\n",ms,mb,e);
        if(j<t) printf("\n");
    }
    return 0;
}

👉最长连续01等串

【题目】

"等串":含有相等数量0和1的01串。

输入一个01串(长度N满足1≤N≤100000),输出最长等子串和等子序列的长度。

【思路】

设母串中含有t0个0和t1个1,则最长等子序列的长度=2*min(t0,t1)

下面求最长等子串的长度。

本题中合法的等子串必须满足含0和1的数量相等,'0'和'1'的作用相互抵消。不妨设0的作用值为-1,1的作用值为1,则合法条件可表述为总作用值为0。用sum[i]代表前i个字符作用值的前缀和,则当sum[l-1]==sum[r]时,子串[l,r]为等子串。

本题sum[i]的取值范围为[-100000, 100000],对于每一个确定的sum[i],当l与r的差值最大时,等子串最长。从前往后寻找满足sum[l]=x的最小的l,结果存储在mint[x]中;从后往前寻找满足sum[r]=x的最大的r,结果存储在maxt[x]中。

cpp 复制代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100000;
const int maxn=N+5;
char t[maxn];int sum[maxn],mint[2*maxn],maxt[2*maxn];
int main(){
    int n,i,ans1,t0,t1,ans2;
    scanf("%d",&n);scanf("%s",t+1);
    t0 = t1 = 0;
    for(i = 1; i<=n; i++)
    	if(t[i] == '0') {t0++;sum[i]=sum[i-1]-1;}
		else {t1++;sum[i]=sum[i-1]+1;}
    ans2 = min(t1, t0)*2;
   	memset(mint,-1,sizeof(mint));
	memset(maxt,-1,sizeof(maxt));
	for(i=0;i<=n;i++){
		int x=sum[i];
		if(mint[N+x]==-1)//数组下标非负化:整体+N
			mint[N+x]=i;//记录最小的a
	}
	for(i=n;i>=0;i--){
		int x=sum[i];
		if(maxt[x+N]==-1) maxt[x+N]=i;//记录最大的b
	}
	ans1=0;
	for(i=0;i<=2*maxn;i++)
		if(mint[i]!=-1)
			ans1=max(ans1,maxt[i]-mint[i]);
    printf("%d %d\n",ans1,ans2);
    return 0;
}

👉300倍数子串

【题目】

输入一个数串s(1≤∣s∣≤10^5),输出是300的倍数的子串数量。

注意:可以有前导和后置0,形式全等、位置不同的子串视为2个子串。

【思路】

注意到是300的倍数也就是既是3的倍数又是100的倍数。3的倍数即各位和为3的倍数,100的倍数即最后两位为0(也有可能就是单个0)。

记sum[i]为前i位的和对3取模的值,s[i]为数串中第i个数的值,那么对于长度大于等于2的子串[l,r],合法条件为sum[l-1]==sum[r-2]且s[r]==s[r-1]==0。单个0也合法。

从小到大枚举子串右界r,并同时记录有多少个0≤x≤r-2分别满足sum[x]=0,1,2。

c 复制代码
#include<stdio.h>
#include<string.h>
#define LL long long
const int N=1e5+5;
char s[N];int sum[N];
int main(){
    int r,n,temp,flag;
    scanf("%s",s+1);
    n=strlen(s+1);
    LL cnt=0;int sl0=1,sl1=0,sl2=0;
    
    sum[0]=0;
    if(s[1]=='0') cnt++; if(s[2]=='0') cnt++;
    if(s[1]=='0'&&s[2]=='0') cnt++; 
    
    for(r=3;r<=n;r++){
        temp=(sum[r-3]+s[r-2])%3;
        if(s[r]=='0'&&s[r-1]=='0')
        flag=1; else flag=0;
        if(temp==0) {sl0++;if(flag) cnt+=sl0;}
        else if(temp==1) {sl1++;if(flag) cnt+=sl1;}
        else {sl2++;if(flag) cnt+=sl2;}
        if(s[r]=='0') cnt++;
        sum[r-2]=temp;
    }
    printf("%lld\n",cnt);
    return 0;
}

👉HDOJ-4561 连续最大积

【题目】

给出一个数列{an},an∈{-2,0,2},计算数列中某一段连续元素的积的最大值。

如果最终的答案小于等于0,直接输出0

若答案是2^x ,输出x。

【思路】

以0为分隔符,选出字串。统计字串的长度,对负数进行处理。

先输入全部数据,再处理。用变量sum统计,其绝对值代表长度,符号代表积的符号。按正序、倒序遍历,根据当前项符号对sum进行处理。

当前a[i]符号 当前sum符号 对sum的处理 符号变化 绝对值变化
a[i]>0 sum<0 sum=sum-1 不变号 绝对值+1
a[i]>1 sum>=0 sum=sum+1 不变号 绝对值+1
a[i]<0 sum<0 sum=-sum+1 变号 绝对值+1
a[i]<1 sum>=0 sum=-sum-1 变号 绝对值+1
a[i]=0 / sum=0 / /

为什么需要从正、反两个方向来遍历:

对于一个序列来说,其中包含的-2个数是确定的。虽然sum绝对值一直在增大,但只有其值为正时才可能对max有贡献。不同的遍历方向,遇到-2的位置不同。

以网友贡献的一组数据为例

数据 -2 2 2 2 -2 -2 2
sum(正序) -1 -2 -3 -4 5 -6 -7
sum(逆序) -7 6 5 4 3 -2 1
c 复制代码
#include<stdio.h>
int a[10010],sum,max;
void fun(int i){
	if (a[i]>0)
		{if(sum<0) sum=sum-1;
		else sum=sum+1;}
	else if (a[i]<0)
		{if (sum<0) sum=-sum+1;
		else sum=-sum-1;}
	else sum=0;
	if (sum>max) max=sum;
}
int main(){
	int T,n,i,k;
	scanf("%d",&T);
	for(k=1;k<=T;k++){
		scanf("%d",&n);
		for(i=0;i<n;i++) scanf("%d",&a[i]);
		max=0;
		sum=0;
		for (i=0;i<n;i++) fun(i);
		sum=0;
		for (i=n-1;i>=0;i--) fun(i);
		printf("Case #%d: %d\n",k,max);
	}
	return 0;
}

排列

全排列

👉三生三世

【题目】

空间限制: 32768K

 题目描述

给出2个玩家的ID,如果后一个ID是前一个ID的全排列的一种,且和前一个ID不一样,

便认为后一个是前一个的前世。现输入这2个玩家的ID,判断后一个是不是前一个的前世。

 输入描述

输入的第一行包含一个整数n,代表字符串ID的长度。(n<=2e5)

接下来两行分别给出一个长度为n的字符串,可能包含所有大写字母及小写字母。

 输出描述

输出yes代表是,no代表不是。

 示例1

输入

4

QBDH

BQDH

输出

yes

 示例2

输入

5

jwjnb

jwjnb

输出

no

【思路】全排列:两个排列所含字符及数量均相同。

c 复制代码
#include<stdio.h>
int main(){
    char a[200005]={0},b[200005]={0};
    int n,i;//n代表ID长度
    int ac[125]={0},bc[125]={0};//ac代表a中包含的各字符数量
    scanf("%d",&n); scanf("%s",&a); scanf("%s",&b);//?
    int same=0;//same代表两字符串中等位相同字符数量
    for(i=0;i<n;i++){
        ac[a[i]-'A']++;bc[b[i]-'A']++;
        if(a[i]==b[i]) same++;
    }
    int flag=1;
    if(same==n) flag=0;//全等
    else{
        for(i='A'-'A';i<='Z'-'A';i++) 
            if(ac[i]!=bc[i]) {flag=0;break;}
        if(i=='Z')
        for(i='a'-'A';i<='z'-'A';i++)
            if(ac[i]!=bc[i]) {flag=0;break;}
    }
    if(flag==1) printf("yes\n");
    else printf("no\n");
    return 0;
}

组合

组合数

【题目】

●题目描述

给一个长为 n 的序列 a1,a2,...,an,从中等概率选出 k 个下标不同的数字,求最小值的期望值。

不难发现期望值乘以C(n,k)之后是一个整数,你只需要输出这个整数对 1000000007 取模后的结果。

这里C(n,k)表示从 n 个数中无序选出 k 个数的方案数,也就是组合数。

●输入描述

第一行是一个正整数T(T≤20),表示测试数据的组数,

对于每组测试数据,

第一行是两个正整数 n,k(n≤1000,k≤n),分别表示序列长度以及选出的数字个数。

第二行包含n个整数ai(0≤ai≤1000)。

●输出描述

对于每组测试数据,输出一行,包含一个整数,表示期望值乘以C(n,k)之后对 1000000007 取模的结果。

●示例1

输入

1

5 2

1 2 3 4 5

输出

20

【思路】
期望值 E ( X ) = ∑ i = 1 C ( n , k ) x i / C ( n , k ) ,所以 E ( X ) ∗ C ( n , k ) = ∑ i = 1 C ( n , k ) x i 为整数。 期望值E(X)=\sum_{i=1}^{C(n,k)}{x_i/}C(n,k),所以E(X)* C(n,k)=\ \sum_{i=1}^{C(n,k)}x_i为整数。 期望值E(X)=i=1∑C(n,k)xi/C(n,k),所以E(X)∗C(n,k)= i=1∑C(n,k)xi为整数。
由于是随机选出组合,因此变换序列中各数的排列顺序并不会改变最终结果,却可以给计算带来很大好处。

对序列中的数从小到大排序,那么对于任何一种选法,其最小值必为前n-k+1个数中的一个。至此,题目简化为依次列举前n-k+1个数。定义循环变量为i(i<=1<= n-k+1),则最小值为i的选法共有C(n-i,k-1)种,最终结果= ∑ i = 1 n − k + 1 a i C ( n − i , k − 1 ) \sum_{i=1}^{n-k+1}a_iC(n-i,k-1) i=1∑n−k+1aiC(n−i,k−1)

实际操作时,需预先生成包含C(n,k)所有取值的表,这里用到了计算组合数的常用公式 C k n = C k n − 1 + C k − 1 n − 1 C{k\atop n}=C{k\atop n-1}+C{k-1\atop n-1} Cnk=Cn−1k+Cn−1k−1

cpp 复制代码
#include<stdio.h>
#include<algorithm>
#define MO 1000000007
using namespace std;
int a[1005],c[1005][1005];//c[n][k]存储组合数C(n,k)取模后的值
int main(){
    int t;
    scanf("%d",&t);
    for(int i=0;i<1005;i++)  c[i][0]=1;
    for(int i=1;i<1005;i++)
        for(int j=1;j<=i;j++)
            c[i][j] =(c[i-1][j]+c[i-1][j-1])%MO;
    while(t--){
        int n,k;
        scanf("%d%d",&n,&k);//n表示序列长度,k表示选出的数字个数
        for(int i=1;i<=n;i++)    scanf("%d",&a[i]);//a存储序列
        sort(a+1,a+n+1);//处理为递增序列
        long long int out = 0;//out存储最终结果
        for(int i=1;i<=n-k+1;i++)//序列中各数
            {out+=a[i]*c[n-i][k-1];out%=MO;}
        printf("%lld\n",out);
    }
    return 0;
}

不相邻组合

👉HDOJ-1205 吃糖果

题目:有一个小朋友叫Gardon,他不喜欢将一样的糖果放在一起吃,喜欢先吃一种,下一次吃另一种。输入各种糖果的类型(最多1000000种)和数量(每种最多1000000个),判断是否存在一种吃糖果的顺序,使得Gardon能以他的方式,把所有糖果都吃完。

思路:排列中不相邻问题使用插空法,剩余糖果能够插满数量最多的糖果之间的所有空隙即可。

c 复制代码
#include<stdio.h>
int main(){
    int T,N,m,p,t;
    __int64 s;//s位数可能超过int存储范围
    scanf("%d",&T);
    while(T--){
        scanf("%d",&N);
        scanf("%d",&m);
        s=0;
        while(--N){
            scanf("%d",&p);
            if(p>m)
            {t=m;m=p;p=t;}
            s+=p;
        }
        if(s>=m-1)
            printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}
相关推荐
轻浮j10 分钟前
Sentinel底层原理以及使用算法
java·算法·sentinel
Abelard_25 分钟前
LeetCode--347.前k个高频元素(使用优先队列解决)
java·算法·leetcode
小猪写代码28 分钟前
C语言:递归函数(新增)
算法·c#
点云SLAM30 分钟前
C++创建文件夹和文件夹下相关操作
开发语言·c++·算法
heeheeai1 小时前
kotlin 函数作为参数
java·算法·kotlin
是十一月末1 小时前
opencv实现KNN算法识别图片数字
人工智能·python·opencv·算法·k-近邻算法
袖清暮雨1 小时前
5_SparkGraphX讲解
大数据·算法·spark
Tisfy1 小时前
LeetCode 3218.切蛋糕的最小总开销 I:记忆化搜索(深度优先搜索DFS)
算法·leetcode·深度优先·题解·记忆化搜索
张明奇-琦玉2 小时前
Boost之log日志使用
linux·服务器·算法
Kai HVZ2 小时前
《机器学习》——利用OpenCV库中的KNN算法进行图像识别
opencv·算法·机器学习