超级传送门:http://codeforces.com/contest/365/problem/C
这场特别遗憾,A题题意看错WA了2次,C题溢出导致没能通过系统测试,如果C题过了就能排前30。
题意是给你一个长度不超过4000的数字s,定义b[i][j]=s[i]*s[j],则可以得到一个矩阵b;又给你一个数a,求b有多少个子矩阵其和为a。
例如:
10
12345
a=10, s=12345
生成的矩阵b为:
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
5 10 15 20 25
b中和为10的子矩阵有以下:
1 2 3 4
--------------
4 6
--------------
10
--------------
4
6
--------------
1
2
3
4
--------------
10
共有6个,故答案为6。
最直接的做法是枚举子矩阵,先抛开计算矩阵元素和不说,光是枚举子矩阵就是O(n^4)的,本题n的规模达到4000,所以此法肯定行不通,需要另辟蹊径,需要思考不枚举子矩阵的做法。
先构造一个数组sum,sum[i]表示s[1]+s[2]+...+s[i](设s下标从1开始),特殊地,为了便于后续计算,另sum[0]=0。
我们可以先用一个二重循环枚举子矩阵的横轴范围,枚举i和j,则当前横轴段的和为sum[j]-sum[i-1],记为val;判断a%val是否为0,如果是,说明由“多行此横轴段”构成的子矩阵满足题意;在矩阵b中,纵轴是放大倍数,我们现在只需要查看纵轴有多少段能放大a/val倍,所以需要预先哈希一下,二重循环就能搞定。
当a为0时需要特殊处理,如果某一横轴段val为0才行,此时纵轴放大倍数任意,即为n*(n+1)/2。
n最大为4000,所以最终答案可能会超int,一定要用long long。
由于s的每一位的值不超过9,所以sum[i]不会超过36000,放大倍数也不会超过36000,如果a/val>36000,那肯定是没有相应的放大倍数的(这份代码里我用了40005)。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
char s[4005];
int sum[4005];
int hash[40005];
int main()
{
freopen("1.txt", "r", stdin);
int t, n;
while (scanf("%d", &n) > 0)
{
sum[0] = 0;
memset(hash, 0, sizeof(hash));
scanf("%s", s);
int len = strlen(s);
for (int i = 0; i < len; i++)
{
s[i] -= '0';
if (i != 0)
{
sum[i + 1] = sum[i] + s[i];
}
else
{
sum[i + 1] = s[i];
}
}
for (int i = 0; i <= len; i++)
{
for (int j = i + 1; j <= len; j++)
{
hash[sum[j] - sum[i]]++;
}
}
long long ans = 0;
for (int i = 0; i <= len; i++)
{
for (int j = i + 1; j <= len; j++)
{
int val = sum[j] - sum[i];
if (n == 0 && val == 0)
{
ans += len * (len + 1) / 2;
}
else if (val != 0 && n % val == 0)
{
if (n / val < 40005)
{
ans += hash[n / val];
}
}
}
}
printf("%I64d\n", ans);
}
return 0;
}