第5章数组和稀疏矩阵 5.1实战题解析 【实战5.1】POJ2189——最多围栏个数 时间限制: 1000ms; 内存限制: 65536KB。 视频讲解 问题描述: 一个条形牧场用p(1≤p≤1000)根柱子(编号分别为1~p)分隔成等间距的围栏,每头牛只能在围栏中放牧,现有n(1≤n≤1000)头牛在放牧。请求出数量不超过c(0≤c≤1000)头牛的最大连续区域的围栏个数。 输入格式: 第1行是3个整数n、p和c。第2行到第n+1行,每行包含一个整数x,取值范围为1~p-1,指定一头牛在柱子x和柱子x+1的围栏中放牧。注意一个围栏中允许放牧多头牛。 输出格式: 在第1行中输出一个整数,表示最多有c头牛的最大连续区域的围栏个数。 输入样例: 2 6 1 2 3 输出样例: 3 解: 假设柱子x和柱子x+1的围栏的编号为x,设置一维数组num[1..p-1],num[i]记录围栏i中放牧的牛数量,题目样例的场景如图5.1所示,围栏的编号为1~5,两头牛分别在围栏2和围栏3中放牧,求出一头牛的最大连续区域的围栏个数为3。 本题就是枚举所有的连续区域,即num[i..j](i≤j),其中包含的围栏个数为j-i+1,求出放牧的牛数量(num[i]+…+num[j]),在所有小于或等于c的牛数量中求最大的围栏个数,对应的时间复杂度为O(p3)。该方法会出现超时,改进方法是采用前缀和。 图5.1样例的场景 设置一维数组sum,sum[i]表示num中的前i项元素和(称为前缀和),即 sum[i]=num[1]+num[2]+…+num[i] 第5章数组和稀疏矩阵 数据结构在线编程实训(C++语言)(全程视频讲解版) 可以在O(p)的时间内求出sum数组,其过程是先置sum的所有元素为0,再通过迭代方式求出sum的每个元素: for(int i=1;i<=p;i++) sum[i]=sum[i-1]+num[i]; 假设i≤j,有: sum[i-1]=num[1]+num[2]+…+num[i-1] sum[j]=num[1]+num[2]+…+num[i-1]+num[i]+…+num[j] 用后式减前式: sum[j]-num[i-1]=num[i]+…+num[j] 其中,sum[j]-num[i-1]表示围栏i到围栏j的连续区域中放牧的牛数量,其中的围栏个数为j-i+1,这样枚举所有的连续区域,即num[i..j](i≤j),在所有小于或等于c的牛数量中求最大的围栏个数。对应的时间复杂度为O(p2)。 对应的程序如下: #include #include const int MAXN=1010; using namespace std; int num[MAXN];   //num[x]表示围栏x中放牧的牛的数量 int sum[MAXN];   //存放前缀和 int main() {int n,p,c,x; while(~scanf("%d%d%d", &n,&p,&c)) {memset(num,0,sizeof num); for(int i=0;i0时循环,取n的末尾二进制位d,若d=0,则跳过; 若d=1,则执行ans*=base,再执行base*=base(向高位移一位的权),并且将n右移一位。最后返回ans。简单地说,base为x的权,按x、x2、x4、……递增,当对应二进制位为1时ans乘上相应的base。该算法称为快速幂算法,求xn的快速幂算法如下: double pow(double x,int n) {double ans=1.0,base=x; while (n!=0) {if ((n&1)==1)   //遇到二进制位1 ans*=base;   //求ans base*=base;   //权递增 n >>= 1;   //n右移一位 } return ans; } 将上述xn中的x改为m×m矩阵A,对应快速幂就是矩阵快速幂算法。对于本题有m=2,两个2×2的矩阵A和B相乘的公式如下: a0,0a0,1 a1,0a1,1b0,0b0,1 b1,0b1,1=a0,0b0,0+a0,1b1,0a0,0b0,1+a0,1b1,1 a1,0b0,0+a1,1b1,0a1,0b0,1+a1,1b1,1 注意: 任何一个矩阵的0次幂为对应的单位矩阵,即 ** **0=10 01 首先置ans为单位矩阵10 01,A=11 10,采用矩阵快速幂算法求出ans=An,再取ans左上角元素(即Fn)模10000得到最终结果。对应的程序如下: #include #include using namespace std; struct Matrix   //表示2×2矩阵类型 { int data[2][2]; }; int n; Matrix multiply(Matrix A,Matrix B)   //返回矩阵A和B相乘的结果 {Matrix C; memset(C.data,0,sizeof(C.data)); for(int i=0;i<2;i++) for(int j=0;j<2;j++) for(int k=0;k<2;k++) {C.data[i][j]+=A.data[i][k]*B.data[k][j]; C.data[i][j]%=10000; } return C; } Matrix quick_pow(Matrix A,int n)   //求矩阵A的n次幂的快速幂算法 {Matrix ans; memset(ans.data,0,sizeof(ans.data)); for(int i=0;i<2;i++)   //置ans为单位矩阵 ans.data[i][i]=1; while(n!=0) {if (n & 1) ans=multiply(ans,A); A=multiply(A,A); n>>=1;   //n右移一位 } return ans; } int main() {while(cin >> n && n!=-1) {if(n==0) {cout << 0 << endl; continue; } Matrix ans; ans.data[0][0]=0;   //先置ans为初始矩阵 ans.data[0][1]=1; ans.data[1][0]=1; ans.data[1][1]=1; ans=quick_pow(ans,n);   //取ans左上角元素 cout << ans.data[0][1]%10000 << endl; } return 0; } 上述程序是AC代码,运行时间为0ms,占用的空间为172KB,编译语言为C++。求ans=xn的快速幂算法和求ans=An的矩阵快速幂算法的对应关系,如表5.1所示。 表5.1快速幂和矩阵快速幂算法的对应关系 比较项 快速幂算法 矩阵快速幂算法 说明 初始化 base=x ans=1 A为m阶矩阵 ans置为m阶单位矩阵 n的二进制位d=0 跳过 跳过 n的二进制位d=1 ans*=base base*=base ans=ans×A A=A×A 由double数乘法改为矩阵乘法 视频讲解 【实战5.3】HDU4920——稀疏矩阵乘法 时间限制: 2000ms; 内存限制: 131072KB。 问题描述: 给定两个n×n的矩阵a和b,求它们的积,结果矩阵中每个元素值模3。 输入格式: 输入包含多个测试用例,每个测试用例的第一行为n(1≤n≤800); 接下来n行,每行n个整数,表示矩阵a,第i行的第j个整数为aij; 类似地,再接下来的n行为矩阵b(0≤aij,bij≤109)。 输出格式: 对于每个测试用例,输出n行,每行n个整数表示a×b的结果。 输入样例: 1 0 图5.3求c=a×b的过程的转换 1 2 0 1 2 3 4 5 6 7 输出样例: 0 0 1 2 1 解: 本题看起来十分平常,直接按矩阵乘法设计算法即可,但会出现超时。 因为题目要求结果矩阵中的每个元素值模3,按求模性质,可以先对a、b的元素值模3,这样a、b中较多的元素为0,可以看成非严格意义上的稀疏矩阵。矩阵a和b的乘法运算的转换过程如图5.3所示。 例如n=2时,计算c=a×b的公式如下: c=a0,0a0,1 a1,0a1,1b0,0b0,1 b1,0b1,1=a0,0b0,0+a0,1b1,0a0,0b0,1+a0,1b1,1 a1,0b0,0+a1,1b1,0a1,0b0,1+a1,1b1,1 常规计算顺序如下: c[0][0]+=a[0][0]*b[0][0] c[0][0]+=a[0][1]*b[1][0] c[0][1]+=a[0][0]*b[0][1] c[0][1]+=a[0][1]*b[1][1] c[1][0]+=a[1][0]*b[0][0] c[1][0]+=a[1][1]*b[1][0] c[1][1]+=a[1][0]*b[0][1] c[1][1]+=a[1][1]*b[1][1] 转换后的计算顺序如下: c[0][0]+=a[0][0]*b[0][0] c[0][1]+=a[0][0]*b[0][1] c[0][0]+=a[0][1]*b[1][0] c[0][1]+=a[0][1]*b[1][1] c[1][0]+=a[1][0]*b[0][0] c[1][1]+=a[1][0]*b[0][1] c[1][0]+=a[1][1]*b[1][0] c[1][1]+=a[1][1]*b[1][1] 之所以做这样的转换是为了提高性能,一方面利用了程序局部性原理,a中元素是按行优先存储的,而按i、j循环处理a[i][j]也是按行优先访问a中的元素; 另外一方面,当a[i][j]=0时无论b[j][k]是多少都不必做乘法运算了,可以优化这种情况的计算。对应的程序如下: #include using namespace std; const int MAXN=805; int a[MAXN][MAXN],b[MAXN][MAXN],c[MAXN][MAXN]; int main() {int n,i,j,k; while (~scanf("%d",&n)) {for(i=0;i using namespace std; const int MAXN=805; int a[MAXN][MAXN],b[MAXN][MAXN],c[MAXN][MAXN]; int main() {int n,i,j,k; while(~scanf("%d",&n)) {for(i=0;i>& matrix) {…} }; 解: 这里要求只能在原数组中做旋转操作,从中找到图像旋转的规律。实现该操作的步骤如下: ① 按左下—右上对角线交换所有对角元素。 ② 将第i行(0≤i >& matrix) {int n=matrix.size();   //行、列数为n for (int i=0;i #include using namespace std; const int MOD=9973; int n,k; struct Matrix   //矩阵类型 { int data[10][10]; } res,A; void init()   //初始化res和A矩阵 {for(int i=0;i>=1;   //k右移一位 } } int main() {int t; cin >> t; while(t--) {scanf("%d%d",&n,&k); init();   //初始化res和A quick_pow();   //用快速幂方法求A矩阵的k次幂 int ans=0; for(int i=0;i #include #include using namespace std; const int MAXN=1007; int t,m,n,x,y; int a[MAXN][MAXN]; int sum[MAXN][MAXN]; int main() {scanf("%d",&t); while(t--) {memset(sum,0,sizeof(sum)); scanf("%d%d%d%d",&n,&m,&x,&y); int ans=0;   //存放x×y子矩阵的最大元素和 for(int i=1;i<=n;i++)   //输入a[i][j] for(int j=1;j<=m;j++) scanf("%d",&a[i][j]); for(int i=1;i<=n;i++)   //求(1,1)~(i,j)矩阵的元素和 for(int j=1;j<=m;j++) sum[i][j]=sum[i-1][j]+sum[i][j-1]+a[i][j]-sum[i-1][j-1]; for(int i=x;i<=n;i++)   //求ans for (int j=y;j<=m;j++) ans=max(ans,sum[i][j]-sum[i-x][j]-sum[i][j-y]+sum[i-x][j-y]); printf("%d\n",ans);   //输出ans } return 0; } 图5.5求(i-x,j-y)~(i,j)矩阵和 上述程序是AC代码,运行时间为249ms,占用的空间为9656KB,编译语言为C++。当然也可以将3个for循环语言合并起来: int ans=0; for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) {scanf("%d",&a[i][j]); sum[i][j]=sum[i-1][j]+sum[i][j-1]+a[i][j]-sum[i-1][j-1]; if (i>=x && j>=y) ans=max(ans,sum[i][j]-sum[i-x][j]-sum[i][j-y]+sum[i-x][j-y]); } 上述采用类似前缀和的方法,对于每个测试用例,求解的时间复杂度为O(mn),如果采用直接枚举所有子矩阵的方法,求解的时间复杂度为O(m2n2)。 5.2.4POJ3213——矩阵乘法问题 视频讲解 时间限制: 5000ms; 内存限制: 131072KB。 问题描述: USTC最近开发了并行矩阵乘法机器PM3,用于非常大的矩阵乘法运算。给定两个矩阵A和B,其中A是n×p矩阵,B是p×m矩阵,PM3可以在O(p(n+p+m))时间内计算矩阵C=A×B。然而,PM3的开发人员很快发现了一个小问题: PM3可能出错,而且每当出现错误时,结果矩阵C只包含一个不正确的元素。 开发人员在PM3给出矩阵C之后检查并纠正它。他们认为这是一项简单的任务,因为最多会有一个不正确的元素。请编写一个程序来检查和纠正PM3计算的结果。 输入格式: 输入的第一行是3个整数n、p和m(0 using namespace std; const int N=1001; int a[N][N]; int b[N][N]; int c[N][N]; int c_row[N]; int b_row[N]; int main() {int n,m,p; while(~scanf("%d%d%d", &n,&m,&p)) {for (int i=0;i #include using namespace std; const int MAXN=1000005; int prime[MAXN];   //用于存放所有的H素数 bool isprime[MAXN];   //isprime[i]为true表示整数i为H素数 int a[MAXN]; void solve()   //求H半素数个数的前缀和数组a {int k=0; memset(isprime,true,sizeof(isprime)); memset(a,0,sizeof(a)); for(int i=5;i1000) break;   //h≤1000001,其最小因子不可能大于1000 for(int j=i;jMAXN) break; a[tmp]=1;   //表示tmp是一个H半素数 } } for(int i=0;i