ABC293解题报告

比赛传送门

E. Geometric Progression

题意:求 i=0N1Ai(modM)A,M109,N1012,不保证质数/互质。

做法一

直接算不好算,但我们可以写出一个递推的形式:设 fn=i=0nAi,则有如下两种转移方式:

  1. fn=fn1+An
  2. fn=Afn1+1

可以通过矩阵乘法来加速这一过程:

  1. [fnAn]=[1A0A]×[fn1An1]
  2. [fn1]=[A101]×[fn11]

使用矩阵快速幂即可将复杂度做到 O(logN)

By tokusakurai

int main() {
    ll A, X, M;
    cin >> A >> X >> M;
 
    mint::set_mod(M);
 
    using mat = Matrix<mint>;
 
    mat a(2, 2);
    a[0][0] = A;
    a[0][1] = 1, a[1][1] = 1;
 
    mat x(1, 2);
    x[0][0] = 1;
 
    x *= a.pow(X);
 
    cout << x[0][1] << '\n';
}

做法二

考虑用分治来推式子。假设 N 为偶数,则有:

i=0N1Ai=i=0N21Ai+AN2i=0N21Ai

于是转化为规模减半的子问题。对于奇数,预先把 AN1 加入答案转化为偶数,或者将答案 ×A+1 即可。复杂度 O(logN)

还有另一种分治方式。同样假设 N 为偶数:

i=0N1Ai=i=0N12A2i+i=0N12A2i+1=i=0N12(A2)i+Ai=0N12(A2)i=(1+A)i=0N12(A2)i

对于奇数同样预先处理一位即可。这种做法的好处是代码较短,因为不需要额外乘一些需要同时维护的系数(如第一种分治的 AN2)。

By Nachia

i64 powm(i64 a, i64 x, i64 m){
    if(x == 1) return 1 % m;
    i64 p = (a + 1) % m;
    i64 q = powm(a * a % m, x/2, m);
    q = q * p % m;
    if(x % 2 == 1) q = (q * a + 1) % m;
    return q;
}
 
int main(){
    i64 A, X, M; cin >> A >> X >> M;
    cout << powm(A, X, M) << endl;
    return 0;
}

做法三

可以根号分块。如果 N 为完全平方数,可以首先 O(N) 计算出 S=i=0N1Ai,答案即为 i=0N1AiNS

如果 N 不是完全平方数,此时只求出了 [0N2) 的和,显然剩下的零散部分大小在 N 级别,直接计算即可。复杂度 O(N)

int A,X,M;
signed main(void) {
	//freopen("m.in","r",stdin);
	//freopen("m.out","w",stdout);
	A=read();X=read()-1;M=read();
	int as=1,ans=0;
	for(int i=0;i<1000000;i++) {
		ans=(ans+as)%M;
		as=as*A%M;
	} 
	int st=0,at=1,aq=0;
	while(st+1000000-1<=X) {
		aq=(aq+ans)%M;
		ans=ans*as%M;
		at=at*as%M;
		st+=1000000;
	}
	while(st<=X) {
		aq=(aq+at)%M;
		at=at*A%M;
		st++;
	}
	printf("%lld",aq);
    return 0;
}

做法四

显然可以使用等比数列求和公式,得到答案为 AN1A1。但是关键在于 M 不是质数,无法直接求逆元。如果我们直接算 (AN1)modMA1,却又不一定是整数。

对于这种情况,有一个技巧:由于最终答案显然为整数,即 AN1A1 的倍数,所以只要令分子的模数也改为 M(A1) 即可。答案即为 (AN1)mod(M(A1))A1modM。容易发现,此时分子取模后的结果仍然为 A1 的倍数,所以可以无需逆元直接除。注意此时模数达到 long long 级别,运算需要开 __int128

在前十名中,大部分使用的是分治做法,其次是矩阵。Rank1 maspy 使用了此技巧,且用了 Python,则可以免去 __int128 等细节,代码极短。

By maspy

A, X, M = map(int, input().split())
if A == 1:
    print(X % M)
else:
    mod = M * (A - 1)
    x = pow(A, X, mod) - 1
    x //= (A - 1)
    print(x % M)

F. Zero or One

题意:输入 n,判断有多少种进制 b,满足 nb 进制下的表示只有 0/1。n1018

合法的 b 最多可以到 n,所以暴力枚举显然不行。但是可以发现,当 b 大一些的时候,位数会极少。例如,如果 b>4000,则位数不会超过五位。

于是,我们先枚举 4000 以下的每个 b,检查是否合法。对于剩下的,位数最多五位,而要求每一位为 0/1,所以“表示”最多只有 25 种。对于每一种表示,显然最多只有一个进制满足条件,检查是否存在即可。这里可以使用二分答案,二分每种进制,检查过大还是过小。

一开始想的是,算出 n 在该进制下的表示,与当前枚举到的 5 位的表示进行比较,但比较难写,于是可以反向考虑:算出当前枚举到的表示,在该进制下的数 n,如果 n>n 则说明进制过大,n<n 则说明进制过小,等于则找到答案。

需要注意的是,由于进制本身已到 long long 级别,在还原表示时还要取幂,所以一定会炸,需要一旦超过 n 就退出循环。还有,这两部分的答案可能有重复(4000 一下也可能存在 5 位的解),需要去重。

By cxm1024

#define int __int128
void Solve(int test) {
	int n = read();
	set<int> s;
	for (int i = 2; i <= 4000; i++) {
		int x = n;
		bool flag = 1;
		while (x) {
			if (x % i > 1) flag = 0;
			x /= i;
		}
		if (flag) s.insert(i);
	}
	for (int i = 1; i < (1 << 5); i++) {
		int l = 2, r = n;
		while (l <= r) {
			int mid = (l + r) / 2;
			int x = 0, now = 1;
			for (int j = 0; j < 5; j++, now *= mid) {
				if ((i >> j) == 0) break;
				if (now > n && (i >> j)) {x = n + 1; break;}
				if (i & (1 << j)) {
					x += now;
					if (x > n) break;
				}
			}
			if (x > n) r = mid - 1;
			else if (x < n) l = mid + 1;
			else {
				s.insert(mid);
				break;
			}
		}
	}
	cout << s.size() << endl;
}

以上代码在实现上比较麻烦,以下讲几个实现上的技巧。

  1. 实际上,需要去重只是因为 4000 的界不够紧,4000 以内存在 5 位的情况。只要令该分界线恰好为 5,6 位的分界线即可。实现中可以枚举 b 的过程中判断 b5n 的大小,超过则 break 即可。
  2. 二分还原时,一旦超过 nbreak 的操作较为繁琐,有一些细节,所以可以直接每一步与 n+1min 来免去这一操作。

By maspy

void solve() {
  LL(N);
  ll ANS = 0;
 
  // 5 乗を使う
  FOR(B, 2, N + 1) {
    if (B * B * B * B * B > N) break;
    vi F;
    ll x = N;
    while (x) {
      F.eb(x % B);
      x /= B;
    }
    if (MAX(F) <= 1) ++ANS;
  }
  // 4 乗以下だけを使う
  FOR(s, 2, 1 << 5) {
    auto f = [&](i128 B) -> i128 {
      i128 pow = 1;
      i128 val = 0;
      FOR(i, 5) {
        if (s >> i & 1) { val += pow; }
        pow *= B;
        chmin(pow, N + 1);
      }
      chmin(val, N + 1);
      return val;
    };
    ll B = binary_search([&](ll B) -> bool { return f(B) <= N; }, 0, N + 1);
    if (1 < B && f(B) == N) { ++ANS; }
  }
  print(ANS);
}

G. Triple Index

题意:有一个长度为 n 的数组,每次询问 [l,r](i,j,k) 的个数,使 i<j<k,ai=aj=akn,q2×105

很难直接处理,于是离线,于是莫队板子。

By cxm1024

#include <bits/stdc++.h>
using namespace std;
#define int long long
int a[200010], t[200010], len = 450;
struct node {
	int l, r, num;
} ask[200010];
int ans[200010];
signed main() {
	int n, q;
	scanf("%lld%lld", &n, &q);
	for (int i = 1; i <= n; i++)
		scanf("%lld", &a[i]);
	for (int i = 1; i <= q; i++) {
		scanf("%lld%lld", &ask[i].l, &ask[i].r);
		ask[i].num = i;
	}
	sort(ask + 1, ask + q + 1, [&](node x, node y) {
		if (x.l / len != y.l / len)
			return x.l / len < y.l / len;
		if (x.l / len % 2 == 0) return x.r < y.r;
		else return x.r > y.r;
	});
	int l = 1, r = 0, now = 0;
	auto add = [&](int x) {
		t[x]++;
		if (t[x] >= 3) now += (t[x] - 1) * (t[x] - 2) / 2;
	};
	auto del = [&](int x) {
		t[x]--;
		if (t[x] >= 2) now -= t[x] * (t[x] - 1) / 2;
	};
	for (auto [ll, rr, num] : ask) {
		while (r < rr) add(a[++r]);
		while (l > ll) add(a[--l]);
		while (r > rr) del(a[r--]);
		while (l < ll) del(a[l++]);
		ans[num] = now;
	}
	for (int i = 1; i <= q; i++)
		printf("%ld\n", ans[i]);
	return 0;
}
posted @   曹轩鸣  阅读(135)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起