小沙的签到题
小沙的签到题
题目描述
给定一段长度为 \(n(n \le 2 \times 10 ^ 5)\) 的序列 \(a(a_i \le 999999)\),求序列中有多少对数相加不会产生 10 进制的进位。
第一种做法是六维前缀和,考虑 s[bit1][bit2][bit3][bit4][bit5][bit6]
表示六位分别低于对应 bit
的方案数
预处理出前缀和数组之后, \(O(1)\) 查询结果,最终答案除以 \(2\) 即可。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i, a, b) for(int i(a); i < b; ++ i)
template <typename T> void chkmax(T &x, T y) { x = max(x, y); }
template <typename T> void chkmin(T &x, T y) { x = min(x, y); }
constexpr int N = 10;
int sum[N][N][N][N][N][N];
int get(char c) {
return c - '0';
}
void add(string s) {
sum[get(s[0])][get(s[1])][get(s[2])][get(s[3])][get(s[4])][get(s[5])] ++;
}
int Sum(string s) {
return sum[get(s[0])][get(s[1])][get(s[2])][get(s[3])][get(s[4])][get(s[5])];
}
void solve() {
int n; cin >> n;
vector<string> a(n), b(n);
for(int i = 0; i < n; i ++ ) {
string s; cin >> s;
reverse(s.begin(), s.end());
while(s.size() < 6) s.push_back('0');
reverse(s.begin(), s.end());
a[i] = s;
add(s);
for(int j = 0; j < 6; j ++ ) {
char &c = s[j];
c = ('9' - c) + '0';
}
b[i] = s;
}
rep(i1, 1, 10) {
rep(i2, 0, 10) {
rep(i3, 0, 10) {
rep(i4, 0, 10) {
rep(i5, 0, 10) {
rep(i6, 0, 10) {
sum[i1][i2][i3][i4][i5][i6] += sum[i1 - 1][i2][i3][i4][i5][i6];
}
}
}
}
}
}
rep(i1, 0, 10) {
rep(i2, 1, 10) {
rep(i3, 0, 10) {
rep(i4, 0, 10) {
rep(i5, 0, 10) {
rep(i6, 0, 10) {
sum[i1][i2][i3][i4][i5][i6] += sum[i1][i2 - 1][i3][i4][i5][i6];
}
}
}
}
}
}
rep(i1, 0, 10) {
rep(i2, 0, 10) {
rep(i3, 1, 10) {
rep(i4, 0, 10) {
rep(i5, 0, 10) {
rep(i6, 0, 10) {
sum[i1][i2][i3][i4][i5][i6] += sum[i1][i2][i3 - 1][i4][i5][i6];
}
}
}
}
}
}
rep(i1, 0, 10) {
rep(i2, 1, 10) {
rep(i3, 0, 10) {
rep(i4, 1, 10) {
rep(i5, 0, 10) {
rep(i6, 0, 10) {
sum[i1][i2][i3][i4][i5][i6] += sum[i1][i2][i3][i4 - 1][i5][i6];
}
}
}
}
}
}
rep(i1, 0, 10) {
rep(i2, 1, 10) {
rep(i3, 0, 10) {
rep(i4, 0, 10) {
rep(i5, 1, 10) {
rep(i6, 0, 10) {
sum[i1][i2][i3][i4][i5][i6] += sum[i1][i2][i3][i4][i5 - 1][i6];
}
}
}
}
}
}
rep(i1, 0, 10) {
rep(i2, 1, 10) {
rep(i3, 0, 10) {
rep(i4, 0, 10) {
rep(i5, 0, 10) {
rep(i6, 1, 10) {
sum[i1][i2][i3][i4][i5][i6] += sum[i1][i2][i3][i4][i5][i6 - 1];
}
}
}
}
}
}
// debug(sum[10][10][10][10][10][10]);
vector<int> ans1(n);
ll sum = 0;
for(int i = 0; i < n; i ++ ) {
bool flag = true;
for(int j = 0; j < 6; j ++ ) {
if((a[i][j] - '0') + (a[i][j] - '0') > 9) {
flag = false;
}
}
if(flag) sum --;
sum += Sum(b[i]);
}
cout << sum / 2;
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
solve();
return 0;
}
第二种解法:和第一种解法思路类似,维护的时候使用六维树状数组即可,注意需要用 \(cnt\) 数组进行去重,即每个数字只计算一次,不然会喜提 TLE。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i, a, b) for(int i(a); i <= b; ++ i)
template <typename T> void chkmax(T &x, T y) { x = max(x, y); }
template <typename T> void chkmin(T &x, T y) { x = min(x, y); }
constexpr int N = 11;
ll tr[N][N][N][N][N][N];
void add(int a[], ll v) {
for(int i1 = a[0]; i1 <= 10; i1 += (i1 & -i1)) {
for(int i2 = a[1]; i2 <= 10; i2 += (i2 & -i2)) {
for(int i3 = a[2]; i3 <= 10; i3 += (i3 & -i3)) {
for(int i4 = a[3]; i4 <= 10; i4 += (i4 & -i4)) {
for(int i5 = a[4]; i5 <= 10; i5 += (i5 & -i5)) {
for(int i6 = a[5]; i6 <= 10; i6 += (i6 & -i6)) {
tr[i1][i2][i3][i4][i5][i6] += v;
}
}
}
}
}
}
}
ll sum(int a[], ll ret = 0) {
for(int i1 = a[0]; i1 > 0; i1 -= (i1 & -i1)) {
for(int i2 = a[1]; i2 > 0; i2 -= (i2 & -i2)) {
for(int i3 = a[2]; i3 > 0; i3 -= (i3 & -i3)) {
for(int i4 = a[3]; i4 > 0; i4 -= (i4 & -i4)) {
for(int i5 = a[4]; i5 > 0; i5 -= (i5 & -i5)) {
for(int i6 = a[5]; i6 > 0; i6 -= (i6 & -i6)) {
ret += tr[i1][i2][i3][i4][i5][i6];
}
}
}
}
}
}
return ret;
}
constexpr int M = 1000010;
void solve() {
int n; cin >> n;
vector<int> cnt(M);
for(int i = 0; i < n; i ++ ) {
int x; cin >> x; cnt[x] ++;
}
for(int i = 0; i < M; i ++ ) if(cnt[i]) {
int b[6]; int x = i;
for(int j = 5; j >= 0; j -- ) {
b[j] = x % 10 + 1;
x /= 10;
}
add(b, cnt[i]);
}
int c[6] = {10, 10, 10, 10, 10, 10};
ll ans = 0;
for(int i = 0; i < M; i ++ ) if(cnt[i]) {
int b[6]; int x = i;
for(int j = 5; j >= 0; j -- ) {
b[j] = x % 10 + 1;
x /= 10;
}
bool flag = true;
for(int j = 5; j >= 0; j -- ) {
if(b[j] + b[j] - 2 > 9) {
flag = false;
}
}
if(flag) ans += 1ll * cnt[i] * (cnt[i] - 1) / 2;
add(b, -cnt[i]);
for(int j = 5; j >= 0; j -- ) {
b[j] = 11 - b[j];
}
ans += sum(b) * cnt[i];
}
cout << ans << "\n";
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
solve();
#ifdef LOCAL
clock_t ends = clock();
cout << "\n\nRunning Time : " << (double) (ends - start) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
#endif
return 0;
}
第三种做法:考虑 dp[i][j]
表示前三位是 \(i\) 并且后三位不会和 \(j\) 产生进位的方案数,然后可以得到一个 \(O(10^6)\) 的 \(DP\),具体过程可以看代码。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
template <typename T> void chkmax(T &x, T y) { x = max(x, y); }
template <typename T> void chkmin(T &x, T y) { x = min(x, y); }
constexpr int N = 1010;
int dp[N][N];
// dp[i][j] 表示高三位为 i,且低三位不会和 j 产生进位的方案数
void solve() {
int n; cin >> n;
ll ans = 0;
for(int i = 0; i < n; i ++ ) {
int num; cin >> num;
int x = num / 1000, y = num % 1000;
x = 999 - x, y = 999 - y;
array<int, 3> a, b;
a[0] = x / 100, a[1] = x / 10 % 10, a[2] = x % 10;
b[0] = y / 100, b[1] = y / 10 % 10, b[2] = y % 10;
x = 999 - x, y = 999 - y;
for(int j = 0; j <= a[0]; j ++ ) {
for(int k = 0; k <= a[1]; k ++ ) {
for(int l = 0; l <= a[2]; l ++ ) {
ans += dp[j * 100 + k * 10 + l][y];
}
}
}
for(int j = 0; j <= b[0]; j ++ ) {
for(int k = 0; k <= b[1]; k ++ ) {
for(int l = 0; l <= b[2]; l ++ ) {
dp[x][j * 100 + k * 10 + l] ++;
}
}
}
}
cout << ans << '\n';
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
solve();
#ifdef LOCAL
clock_t ends = clock();
cout << "\n\nRunning Time : " << (double) (ends - start) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
#endif
return 0;
}