Codeforces Round #492 (Div. 1) [Thanks, uDebug!] E - Number Clicker

题意:

给出u,v,p三个正整数,有三种操作u=(u+1)%p,u=(u+p-1)%p,u=pow(u,p-2)%p,找到一种方法使得200步之内通过这三种操作令u变为v,保证有解

分析:

相当于一个每个p个节点每个节点最多3条边的图让你找两点之间长度不大于200的路径

实际上逆元这条边相当随机,好像是有人研究过这类问题的,贴一下CF官方题解的说法:

We present two solutions, which both essentially use the fact that the graph is almost "random". This follows from some known number theoretic results on expander graphs (keyword is "Margulis expanders").

我使用的是官方给出的方法1:

随机sqrt(p)条长为100的从u出发的路径并记录,再从v开始随机sqrt(p)条长至多为100的路径直到与之前路径中走到的点重复

那么为什么这样做可以呢?

我们先打一打第一步的表,发现在p=1e9+7时,随机4e4条长为100的路径,大约可以得到2.5e6个不同的点,打表程序如下

#include<bits/stdc++.h>
#define pii pair<int, int>
#define fi first
#define se second
#define mk make_pair
#define sc(x) scanf("%d", &x)
#define pb push_back
#define debug puts("???")
#define ABS(x) ((x)<0?(-x):(x));
typedef long long LL;
using namespace std;

const int mod = 1e9+7;
const int maxn = 4e4+10;

int u, p = mod;
unordered_set<int> vis, ans;

int qpow(LL x, LL k){
    LL res = 1;
    while(k > 0){
        if(k & 1) res = res*x%p;
        x = x*x%p;
        k >>= 1;
    }
    return res;
}

void run(){
    int now = u, to[3], sz;
    vis.clear();
    for(int i = 0; i < 100; i++){
        sz = 0;
        if(vis.find((now+1)%p) == vis.end()) to[sz++] = (now+1)%p;
        if(vis.find((now+p-1)%p) == vis.end()) to[sz++] = (now+p-1)%p;
        int pw = qpow(now, p-2);
        if(vis.find(pw) == vis.end()) to[sz++] = pw;
        if(sz == 0){
            to[sz++] = (now+1)%p;
            to[sz++] = (now+p-1)%p;
            to[sz++] = pw;
        }
        int id = rand()%sz;
        now = to[id];
        vis.insert(now);
    }
    for(int x: vis) ans.insert(x);
}

void work(){
    ans.clear();
    for(int t = 0; t < maxn; t++) run();
    printf("%d\n", ans.size()); ///2.5e6
}

int main(){
    srand(time(NULL));
    for(u = 0; u < 100; u++){
        work();
    }
    return 0;
}

在这之后,我们来计算一下概率,从v出发走100步不会和之前重合的概率相当于随机100个数且与之前2.5e6个数不重复,单次的概率大约为p=0.77(很违反直觉?可以搜索生日悖论),然后我们重复这个操作4e4次都不重复的概率相当于pow(p, 4e4),几乎是不可能事件了,算这个概率的程序如下

#pragma GCC optimize(3)
#include<bits/stdc++.h>
#define sc(x) scanf("%d", &x)
#define pb push_back
#define fi first
#define se second
#define pii pair<int, int>
#define mk make_pair
#define debug puts("???")
#define frein freopen("in.txt", "r", stdin)
#define freout freopen("out.txt", "w", stdout)
#define freout1 freopen("out1.txt", "w", stdout)
using namespace std;

typedef long long LL;
const int INF = 0x3f3f3f3f;
const int mod = 1e9+7;

int main(){
    double p = 1;
    int sz = 2.5e6;
    for(int i = 0; i < 100; i++){
        p *= mod-sz-i;
        p /= mod;
        printf("%.6f\n", p);
    }
    double ans = 1;
    for(int i = 0; i < 100; i++){
        ans *= p;
    }
    printf("%.6f\n", ans);
    return 0;
}

 

确认了这么做非常可行以后,就可以实现了

#include<bits/stdc++.h>
#define pii pair<int, int>
#define fi first
#define se second
#define mk make_pair
#define sc(x) scanf("%d", &x)
#define pb push_back
#define debug puts("???")
#define ABS(x) ((x)<0?(-x):(x));
typedef long long LL;
using namespace std;

const int mod = 1e9+7;
const int maxn = 4e4+10;

int u, m, p = mod;
unordered_set<int> vis;
map<int, pii> mp;
vector<int> path[maxn], v;

int qpow(LL x, LL k){
    LL res = 1;
    while(k > 0){
        if(k & 1) res = res*x%p;
        x = x*x%p;
        k >>= 1;
    }
    return res;
}

void run(int x){
    int now = u, sz;
    pii to[3];
    vis.clear();
    for(int i = 0; i < 100; i++){
        sz = 0;
        if(vis.find((now+1)%p) == vis.end()) to[sz++] = mk((now+1)%p, 1);
        if(vis.find((now+p-1)%p) == vis.end()) to[sz++] = mk((now+p-1)%p, 2);
        int pw = qpow(now, p-2);
        if(vis.find(pw) == vis.end()) to[sz++] = mk(pw, 3);
        if(sz == 0){
            to[sz++] = mk((now+1)%p, 1);
            to[sz++] = mk((now+p-1)%p, 2);
            to[sz++] = mk(pw, 3);
        }
        int id = rand()%sz;
        now = to[id].fi;
        path[x].pb(to[id].se);
        vis.insert(now);
        mp[now] = mk(x, i+1);
    }
}
void output(int x, int y){
    printf("%d\n", v.size()+y);
    for(int i = 0; i < y; i++) printf("%d ", path[x][i]);
    for(int i = v.size()-1; i >= 0; i--) printf("%d%c", v[i], " \n"[i!=0]);
}

int get(){
    int now = m, sz;
    pii to[3];
    vis.clear(); v.clear();
    for(int i = 0; i < 100; i++){
        sz = 0;
        if(vis.find((now+1)%p) == vis.end()) to[sz++] = mk((now+1)%p, 2);
        if(vis.find((now+p-1)%p) == vis.end()) to[sz++] = mk((now+p-1)%p, 1);
        int pw = qpow(now, p-2);
        if(vis.find(pw) == vis.end()) to[sz++] = mk(pw, 3);
        if(sz == 0){
            to[sz++] = mk((now+1)%p, 2);
            to[sz++] = mk((now+p-1)%p, 1);
            to[sz++] = mk(pw, 3);
        }
        int id = rand()%sz;
        now = to[id].fi;
        v.pb(to[id].se);
        vis.insert(now);
        if(mp.find(now) != mp.end()){
            output(mp[now].fi, mp[now].se);
            return 1;
        }
    }
    return 0;
}
void work(){
    int up = sqrt(p);
    for(int t = 0; t < up; t++) run(t);
    for(int t = 0; t < up; t++) if(get()) return;
}

int main(){
    srand(time(NULL));
    sc(u); sc(m); sc(p);
    work();
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值