【离散对数 && 逆元 && 概率论】UVA

来源:互联网 发布:java高达 编辑:程序博客网 时间:2024/05/22 00:07

Problem Description

有这样一道题,要给一个M行N列的网格涂上K种颜色,其中有B个格子不能涂色。其他每个格子涂一种颜色,同一列中的上下两个相邻格子不能涂相同颜色。给出M, N, K 和 B个格子的位置,求出涂色方案总数除以100000007的结果R。
本题的任务和这个相反:已知N, K, R 和 B个格子的位置,求最小的可能的M。

思路:

一列一列地涂色,每列从上往下涂。如果一个格子位于第一行,或者它上面相邻格子不能涂色,则它有K种涂色方法其他可涂色格有K-1种方法。虽然M是未知的。但由于M至少应当等于不能涂色的格子的行编号的最大值,因此可以把整个网络分成不变部分和可变部分
假定不变部分和可变部分的第一行一共有cnt种涂色法则每加一行之后,涂色方案数都会乘以P = (K - 1)^N。这样,我们得到了一种模方程cnt*P^M = R, 移项得P^M = R * cnt^-1。用大步小步算法求解即可。注意要事先判断 M = L是否满足条件,否则不仅“可变部分”为空,“不变部分”也是不完整的。

求离散对数 大步小步算法
解模方程a^x ≡ b(mod n) 求解 x。我们只讨论n为素数的情况
因为n是素数,只要a不为0, 一定存在逆a^-1
根据欧拉定理,只需检查x = 0, 1, 2, …, n-1是不是解即可。因为a^(n-1) ≡ 1(mod n), 当x超过n-1时a^x就开始循环了,我们先检查前m项(m = sqrt(n+0.5)),即a^0, a^1, …, a^(m-1)模n的值是否为b,并把a^i mod n保存在ei里,求出a^m的逆a^(-m)
下面考虑a^m, a^(m+1), …, a^(2*m - 1)。这次不用一一检查,因为如果它们中有解,则相当于存在i使得e^i * a^m ≡ b(mod n)。两边同乘a^(-m)得ei ≡ bb(mod n), 其中bb = a^(-m)*b(mod n)。这样只需检查是否真的存在ei等于这个bb即可
复杂度O(n) 降低到(sqrt(n)logn)

#include<bits/stdc++.h>using namespace std;#define LL long longconst int maxn = 510;const LL mod = 100000007;int x[maxn], y[maxn], n, k, b, r, m;struct node{    int xx, yy;    bool operator < (const node &b) const{        if(xx == b.xx) return yy < b.yy;        else return xx < b.xx;    }};set< node > q;LL mul_mod(LL a, LL b){    return (a*b)%mod;}LL Pow(LL a, LL n){    LL sum = 1;    while(n)    {        if(n&1) sum = mul_mod(sum, a);        a = mul_mod(a, a);        n >>= 1;    }    return sum;}void extend_gcd(LL a, LL b, LL &d, LL &x, LL &y){    if(!b){d = 1; x = 1; y = 0;}    else {extend_gcd(b, a%b, d, y, x); y -= x*(a/b);}}LL inv(LL a, LL n)//求逆元 ax≡1modn,x是a的逆元{    LL d, x, y;    extend_gcd(a, n, d, x, y);    return d == 1 ? (x+n)%n : -1;}int log_mod(int a, int b)//求离散对数{    int m, v, e = 1, i;    m = (int)sqrt(mod+0.5);//避免浮点误差    v = inv(Pow(a, m), mod);//求a^m的对mod取模 的逆元    map<int, int> x;    x[1] = 0;//e0=a^0%mod, 所以x[a^0] = 0    for(i = 1; i < m; i++)    {        e = mul_mod(e, a);//a^i%mod        if(!x.count(e)) x[e] = i;//i对应的是几次方    }    for(i = 0; i < m; i++)    {        if(x.count(b)) return i*m + x[b];//如果存在b`== ei,有解返回x[b]次方 + 多少个m次方        b = mul_mod(b, v);    }    return -1;}int Count()//求m行的时候有多少种方案{    int num = 0, i;//num 用来记录涂k种方法的格子有多少个    for(i = 0; i < b; i++)    {        if(x[i] != m && !q.count((node){x[i]+1, y[i]}))//每个不能涂色的格子的下一行都能涂k种方法,因为只有m行,所以x[i] != m            num++;    }    num += n;//第一行的格子能涂k种颜色    for(i = 0; i < b; i++) {        if(x[i] == 1) num--;//第一行不能涂色的格子删除    }    return mul_mod(Pow(k, num), Pow(k-1, (LL)n*m-num-b));//返回涂色方案数}int doit(){    int cnt = Count(), i;//求m行的时候有多少种方案    if(cnt == r) return m;//满足输出    int num = 0;//m+1行有多少个可以涂K种颜色的格子    for(i = 0; i < b; i++) {        if(x[i] == m) num++;    }    m++;    cnt = mul_mod(cnt, Pow(k, num));    cnt = mul_mod(cnt, Pow(k-1, n-num));//更新m+1行后的总方案数    if(cnt == r) return m;//满足输出    return log_mod(Pow(k-1, n), mul_mod(r, inv(cnt, mod))) + m;//可变部分}int main(){    int T, Case = 1;    scanf("%d", &T);    while(T--)    {        scanf("%d %d %d %d", &n, &k, &b, &r);        m = 1; q.clear();//初始化,m至少为1        for(int i = 0; i < b; i++)        {            scanf("%d %d", &x[i], &y[i]);            m = max(x[i], m);//找出不能涂色的点的最大的行            q.insert((node){x[i], y[i]});//将所有点存起来        }        printf("Case %d: %d\n", Case++, doit());    }    return 0;}