冷滟泽的个人博客冷滟泽的个人博客

CF995E Number Clicker

官方题解里提到验题人的一种期望更优的作法,感觉很有意思。

考虑从 uv 分别找出一条到 0 的长度在 100 以内的路径。那么以 u 为例,我们在区间 [1, p-1] 内随机取一个整数 x ,记 a=ux\mod p,b=u。对 (a,b) 使用更相减损术将 b 迭代到 0 为止,具体过程如下:

  • a<b ,交换 ab
  • 否则,a-=b

观察到初始时 u\equiv\frac{a}{b}(\mod p),那么操作 u\to u-1(\mod p) 就相当于 \frac{a}{b}\to \frac{a-b}{b}(\mod p)u\to u^{p-2}(\mod p) 就相当于 \frac{a}{b}\to \frac{b}{a}(\mod p)。到 b=0u 也就为 0 了。

但更相减损术不能保证复杂度。于是我们不断随机 x 并用辗转相除法计算路径长度,直到它小于等于 100 为止。实际上这样跑的次数会很少,几乎是期望 O(1) 的。所以我们可以在期望 O(\log p) 左右的时间找到一条从任一点到 0 的长度在 100 以内的路径。

因为 2 操作与 3 操作互逆,那么显然 v 到 0 的路径是可逆的。最终答案就是把两条路径拼起来。

下面是代码:

#include <cstdio>
#include <algorithm>
#include <chrono>
using namespace std;
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
const int MAXL=220;
int get_len(int a, int b)
{
    if (b==0) return 0;
    return get_len(b, a%b)+a/b+1;
}
int get_path(int u, int p, bool* c)
{
    int a, b, l=0;
    do
    {
        b=rng()%(p-1)+1;
        a=1ll*u*b%p;
    } while (get_len(a, b)>100);
    while (b!=0)
        if (a<b) c[++l]=0, swap(a, b);
        else a-=b, c[++l]=1;
    return l;
}
bool c1[MAXL], c2[MAXL];
int main()
{
//  freopen("E.in", "r", stdin);
//  freopen("E.out", "w", stdout);
    int u, v, p;
    scanf("%d%d%d", &u, &v, &p);
    int l1=get_path(u, p, c1);
    int l2=get_path(v, p, c2);
    printf("%d\n", l1+l2);
    for (int i=1; i<=l1; i++) printf("%d ", c1[i]?2:3);
    for (int i=l2; i>=1; i--) printf("%d ", c2[i]?1:3);
    putchar('\n');
    return 0;
}
未经允许不得转载:冷滟泽的个人博客 » CF995E Number Clicker

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址