【离散对数 && 逆元 && 概率论】UVA - 11916 Emoogle Grid

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 long
const 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值