例58 连续子序列计数
问题描述
给定一个正整数序列,对其和可被给定整数d整除的所有连续子序列进行计数。这些子序列可能重叠。例如,序列 2, 1, 2, 1, 1, 2, 1, 2包含6个连续的子序列,其总和可被4整除:6个子序列为:第一到第八个数、第二到第四个数、第二到第七个数、第三到第五个数、第四到第六个数和第五到第七个数。
输入
输入的第一行由一个整数c(1<=c<=200)组成,即测试用例的数量。
每个测试用例包括两行,第1行由两个整数d(1<=d<=1 000 000)和n(1<=n<=50 000)组成,分别是子序列和的除数d和序列长度。测试用例的第2行包含序列的n个元素,它们是介于1和1000000之间的整数。
输出
对于每个测试用例,输出连续子序列的和可被d整除的子序列个数。
输入样例
2
7 3
1 2 3
4 8
2 1 2 1 1 2 1 2
输出样例
0
6
(1)编程思路。
定义数组long long num[1000005]={0},其中数组num[i]保存前缀和除以d的余数为i的个数。
输入给定的n个整数时,一边输入,一边求出前缀和sum,再计算sum除以d的余数,对应的num[sum%d]元素值加1。余数为0的子序列一定能整除d。而余数相同的任意两个子序列相减,得到的子序列也一定能被d整除。
所以用循环遍历所有的余数个数(即num[0]~num[d-1]),将num[i] *(num[i]-1)/2的值累加起来(两两组合),再加上num[0]的值,就是所求的答案。
(2)源程序。
#include <stdio.h>
#include <string.h>
long long num[1000005];
int main()
{
int t;
scanf("%d",&t);
while (t--)
{
long long ans=0;
long long sum=0;
long long a;
memset(num,0,sizeof(num));
long long d,n;
scanf("%lld%lld",&d,&n);
int i;
for (i=1;i<=n;i++)
{
scanf("%lld",&a);
sum+=a;
sum%=d;
num[sum]++;
}
ans+=num[0];
for (i=0;i<d;i++)
{
if(!num[i]) continue;
ans+=num[i]*(num[i]-1)/2; // 两两组合
}
printf("%lld\n",ans);
}
return 0;
}
习题58
58-1 越狱
题目描述
*有 n 个房间,每个房间关押一个犯人,有 m 种宗教,每个犯人会信仰其中一种。如果相邻房间的犯人的宗教相同,就可能发生越狱,求有多少种状态可能发生越狱。
输入格式
输入只有一行两个整数,分别代表宗教数 m(1≤m≤108) 和房间数 n(1≤n≤1012)。
输出格式
输出一行一个整数代表答案。答案对 100,003取模。
输入样例
2 3
输出样例
6
(1)编程思路。
n 个房间m 种宗教,每个房间犯人的宗教信仰选择都有m种,按乘法原理知总的排列数为mn,这么多情况进行排列看哪些情况可能越狱不是很方便。本题可以采用间接的方法,即可能发生越狱的方案数=总的排列数-不会越狱的方案数。
求不会越狱的方案数就比较简单了,第1个房间有m种宗教信仰选择,而后面的n-1个房间都有m-1种可能选择(由于必须满足同种宗教不相邻,因此第2个房间可选除了第1个房间已选之外的m-1种宗教信仰,第3个房间可选除了第2个房间已选之外的m-1种宗教信仰,…),由乘法原理知:不会越狱的方案数=m∗(m−1)n−1
因此,可能发生越狱的方案数为: mn−m∗(m−1)n−1
由于m和n数值较大,采用快速幂完成幂运算。
(2)源程序。
#include <stdio.h>
#define MOD 100003
long long quickPower(long long x,long long n)
{
long long p=1;
while(n)
{
if (n%2==1) p=p*x%MOD;
x=x*x%MOD;
n=n/2;
}
return p;
}
int main()
{
long long n,m;
scanf("%lld%lld",&m,&n);
printf("%lld",(quickPower(m,n)-m*quickPower(m-1,n-1)%MOD+MOD)%MOD);
return 0;
}
58-2 数字计数
问题描述
给定两个整数a和b,将a和b之间的数字(包括a和b)写在一个列表中。您的任务是计算列表中每个数字出现的次数。例如,a=1024,b=1032,则列表为
1024 1025 1026 1027 1028 1029 1030 1031 1032
列表中有10个0、10个1、7个2、3个3、4~9各1个。
输入
输入最多由500行组成。每行包含两个数字a和b,其中0<a,b<100000000。输入由一行“0”终止,该行不被视为输入的一部分。
输出
对于每对输入,输出一行,其中包含由单个空格分隔的十个数字。第一个数字是数字0的出现次数,第二个数字是数字1的出现次数,以此类推。
输入样例
1 10
44 497
346 542
1199 1748
1496 1403
1004 503
1714 190
1317 854
1976 494
1001 1960
0 0
输出样例
1 2 1 1 1 1 1 1 1 1
85 185 185 185 190 96 96 96 95 93
40 40 40 93 136 82 40 40 40 40
115 666 215 215 214 205 205 154 105 106
16 113 19 20 114 20 20 19 19 16
107 105 100 101 101 197 200 200 200 200
413 1133 503 503 503 502 502 417 402 412
196 512 186 104 87 93 97 97 142 196
398 1375 398 398 405 499 499 495 488 471
294 1256 296 296 296 296 287 286 286 247
(1)编程思路。
编写函数long long calc(long long n, int d)求1~n这n个整数的序列中数字d的个数。
下面以calc(3124,2)的计算为例进行说明。
从低位向高位处理:当处理个位时,可把个位变成2,那么前面有多少个数,那么就有多少个2,前面有0~312共有313个数,sum+=313,sum=313;
之后处理十位,2前面若取0~30,则个位可取0~9,共有31*10个,若前面是31时,十位后面可以取0,1,2,3,4,共有5个数;因此,sum+= 31*10 +5,sum=628;
之后处理百位:1前面可以取0~2共有3个数,这时百位可以取2(总是比3124小),后面可以取任意的数(00~99),这样可以取3*100个,百位前面若取3时,因为百位的1小于2,后面没法取;因此,sum+=3*100,sum=928;
最后处理千位,当千位为2时,后面可以取任意的数(000~999),这样,sum+=1000,sum=1928。
故 calc(3124,2)的返回值为1928。
(2)源程序。
#include <stdio.h>
long long num[12]={1LL};
long long calc(long long n, int d)
{
long long sum=0,left,a;
int i;
for (i=1; i<12 ; i++)
{
left=n/num[i];
if (d==0) left--;
sum+=left*num[i-1];
a=(n%num[i]-n%num[i-1])/num[i-1];
if (a>d) sum+= num[i-1];
else if (a==d) sum+=n%num[i-1] +1;
if (n<num[i]) break;
}
return sum;
}
int main()
{
int i;
for (i=1;i<12;i++)
num[i]=num[i-1]*10;
long long n,m;
while (scanf("%lld%lld",&n,&m ) && (n||m))
{
if (m>n)
{
long long t; t=n; n=m; m=t;
}
for (i=0; i<=9; i++)
printf("%lld ",calc(n,i)-calc(m-1,i));
printf("\n");
}
return 0;
}
58-3 选3个点
问题描述
给定n个点,各点的坐标均为整数。任选3个点(x1,y1)、(x2,y2)和(x3,y3),设
A=|x1y2 - y1x2 + x2y3 - y2x3 + x3y1 - y3x1|/2
试求所选取的3个点使得A为整数(0也算)的组合有多少种?
输入
第一行包含测试用例的个数t。之后每一个测试用例占1行,该行中第一数是不同点的个数n(0 <n= 10000),紧接着是n对整数,每对描述一个点(Xi,Yi),并且-100000 <=Xi,Yi=100000。所有这些数字都用一个空格隔开。
输出
使用包含“Scenario#i:”的行开始每个测试用例的输出,其中i是从1开始的用例编号。然后打印一行,其中包含使得A为整数的3个点的组合种数。
输入样例
6
3 0 0 2 0 1 -3
3 0 0 2 1 1 -3
3 0 0 2 2 3 3
4 0 0 2 0 0 2 2 2
4 0 0 1 0 0 1 1 1
9 0 0 0 1 0 2 1 0 1 1 1 2 2 0 2 1 2 2
输出样例
Scenario #1:
1
Scenario #2:
0
Scenario #3:
1
Scenario #4:
4
Scenario #5:
0
Scenario #6:
48
(1)编程思路。
A=|x1y2 - y1x2 + x2y3 - y2x3 + x3y1 - y3x1|/2
=| y1*(x3-x2) + y2*(x1-x3) + y3*(x2-x1)|/2
要使A为整数,只有当y1*(x3-x2) + y2*(x1-x3) + y3*(x2-x1)为偶数时才成立。
对于任一坐标(x,y),可以通过(x&1,y&1)将其转换为4种类型之一。
1)(0,0)表示坐标(x,y)中x、y均为偶数;
2)(0,1)表示坐标(x,y)中x为偶数,y为奇数;
3)(1,0)表示坐标(x,y)中x为奇数,y为偶数;
4)(1,1)表示坐标(x,y)中x、y均为奇数。
这样对于每个点的坐标,可以将它归属为4种类型之一,用数组num[i][j]保存各类型点的个数。
显然,若在这4种类型的同种类型中任意选3个,则A肯定为整数。例如,若三个点均属于(1,1)类型,即x1、y1、x2、y2、x3、y3均为奇数,则(x3-x2)、(x1-x3)、(x2-x1)均为偶数,所以 y1*(x3-x2) + y2*(x1-x3) + y3*(x2-x1)也是偶数。
若在某种类型中任选2个点,再在另一类型中选一个点,则A也是整数。因为在公式计算中3个点坐标可以看成无差别,因此不妨设点(x1,y1)和(x2,y2)类型相同,点(x3,y3)与它们类型不同。若点(x1,y1)和(x2,y2)的类型为(0,0)或(1,0),y1和y2均为偶数,x1和x2同奇偶,(x2-x1)为偶数,不管点(x3,y3)类型如何,y1*(x3-x2) + y2*(x1-x3) + y3*(x2-x1)一定是“偶数+偶数+偶数”,也是偶数;若点(x1,y1)和(x2,y2)的类型为(0,1)或(1,1),y1和y2均为奇数,x1和x2同奇偶,(x2-x1)为偶数,若点(x3,y3)选择时x3与x1、x2同奇偶,(x3-x2)和(x3-x1)均为偶数,y1*(x3-x2) + y2*(x1-x3) + y3*(x2-x1)一定是“偶数+偶数+偶数”,也是偶数;若点(x3,y3)选择时x3与x1、x2不同奇偶,(x3-x2)和(x3-x1)均为奇数,y1*(x3-x2) + y2*(x1-x3) + y3*(x2-x1)一定是“奇数+奇数+偶数”,也是偶数。
若在4种类型中任选3种类型,每种类型中选1个点,则A不会是整数。例如点(x1,y1)、(x2,y2)和(x3,y3)的类型分别为(0,0)、(0,1)和(1,0)。则y1、y3是偶数,y2是奇数,x1是偶数,x3是奇数,(x3-x1)为奇数,y1*(x3-x2) + y2*(x1-x3) + y3*(x2-x1)一定是“偶数+奇数+偶数”,是奇数,A不会是整数。其他组合分析类似。
由此可得:如果选取的3个点存在2个以上的类型相同,那么A一定是整数。
(2)源程序。
#include <stdio.h>
#include <string.h>
int main()
{
int t;
scanf("%d",&t);
int iCase=1;
while(t--)
{
int n;
scanf( "%d",&n );
int num[2][2];
memset(num,0,sizeof(num));
int i,j;
for (i=0; i<n; i++)
{
int x,y;
scanf("%d%d",&x,&y);
num[x&1][y&1]++;
}
long long sum=0;
for (i=0; i<2; i++)
for (j=0;j<2;j++)
{
long long temp=num[i][j];
sum += temp*(temp-1)*(temp-2)/6 + temp*(temp-1)*(n-temp)/2;
}
printf("Scenario #%d:\n%lld\n",iCase++,sum);
}
return 0;
}