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
#define pii pair
#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 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
#define sc(x) scanf("%d", &x)
#define pb push_back
#define fi first
#define se second
#define pii pair
#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
#define pii pair
#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 vis;
map mp;
vector 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;
}

 

你可能感兴趣的:(思维,数论)