Emoogle Grid UVA - 11916
这道题需要求离散对数取模,具体的求解算法是BSGS算法
如果不了解,可以先看一下网上一篇博客写的很清楚
BSGS算法
题目意思:
题意:要给M行N列的网格染上K种颜色,其中有B个不用染色,其他每个格子涂一种颜色,同一列上下两个格子不能染相同的颜色,给出M,N,K,和B个格子的位置,求出涂色的方案%100000007的结果是R,现在给出N,K,R和B个格子位置,让你求最小的M
题目分析:
我们尝试着一列一列的染色,发现这道题的染色方法其实非常简单,因为它只有两个限定条件
1.上下两个相邻格子不能同色
2. 有B个不能涂色到格子
因此涂色方案也非常到好找,我们发现每个可以涂色的格子,其可能涂色方案数要么是k,要么是k-1。
1、当在第一行时,因为不受上一行影响(没有上一行),能涂色到格子的方案数是k种
2、当恰好在不能涂色到格子下一个时,因为不受上一个不能涂色到格子的影响,这个格子的方案数是k种
3、其他能够涂色到格子到种类都是k-1种
我们可以比较容易想到的是M最起码要和B个格子中X坐标最大的一样假设这个最大行是max,而且根据上面的分析我们可以发现,从max行往上的这部分所有格子的方案数其实是固定的,我们完全可以算出来,即算出k-1种的格子多少个,k种到格子多少个,乘起来就是这部分的总方案数即 cnt=kc1(k−1)c2 c n t = k c 1 ( k − 1 ) c 2 ,这个时候我们可以检测一下是否和所给答案相同,相同直接输出答案。
如果不同:
如上分析,max行往下到部分是变化的,首先第max+1行,受到max行中不能涂色到格子的影响,所以我们单独算出来这一行的方案数num,然后乘到之前部分的方案数中,即
cnt=cnt∗num
c
n
t
=
c
n
t
∗
n
u
m
,这个时候我们在检查是否符合答案,符合输出。
如果不同:
下面就简单了,每一行都是
(k−1)n
(
k
−
1
)
n
种,n是列,每个格子都受上一行影响,所以是k-1种。设
a=(k−1)n
a
=
(
k
−
1
)
n
,我们设还需要增加M行,则这M行的格子的总的种类数就是
a⋅a...⋅a=aM
a
⋅
a
.
.
.
⋅
a
=
a
M
,再乘上原来那部分种类数我们就得到了同余式
cnt⋅aM≡R mod( mo )
c
n
t
⋅
a
M
≡
R
m
o
d
(
m
o
)
即 aM≡R⋅cnt−1 mod( mo ) a M ≡ R ⋅ c n t − 1 m o d ( m o ) (其中 cnt−1 c n t − 1 是cnt的逆元)
这样我们到任务就是求M,这就是离散对数取模,需要用BSGS算法
我们继续设 b=R⋅cnt−1 b = R ⋅ c n t − 1
得到 aM≡b mod( mo ) a M ≡ b m o d ( m o )
上面我贴了一篇博客网址,其算法步骤如下:
1 设
M=i×m+j
M
=
i
×
m
+
j
其中
m=⌈mo‾‾‾√ ⌉,i=Mm
m
=
⌈
m
o
⌉
,
i
=
M
m
,
j=M%m
j
=
M
%
m
2 先枚举 j(j∈[0,m]) j ( j ∈ [ 0 , m ] ) 算出 aj a j 用hash存下来 并记录对应j
因为 aM=(am)i⋅aj a M = ( a m ) i ⋅ a j , aj a j 已经枚举完并记录下来了
3 枚举i,
am
a
m
可以提前算出来,所以每次枚举i算出
(am)i=A
(
a
m
)
i
=
A
A⋅aj≡b mod(mo)
A
⋅
a
j
≡
b
m
o
d
(
m
o
)
所以每次枚举为们就可以用扩展欧几里徳求同余式方程求出
aj
a
j
,然后在已经存好的hash中查找是否存在,存在就说明我们找到了,这样i,j都有了我们就求出了
M=i×m+j
M
=
i
×
m
+
j
以上便是这个算法的完整过程
但是我们会发现一个问题,就是枚举i的过程中,每次都需要用扩展欧几里徳求一遍感觉有些麻烦可以简化一些吗?
我们观察一下第三步,看有什么可以改进到地方
第三步中我们需要求
A⋅aj≡b mod(mo)
A
⋅
a
j
≡
b
m
o
d
(
m
o
)
因为第一个式子原式是
(am)i⋅aj≡b mod(mo)
(
a
m
)
i
⋅
a
j
≡
b
m
o
d
(
m
o
)
枚举i每增加一相当于左边多乘了一个
am
a
m
那么是不是就相当于每次右边多乘一个
(am)−1
(
a
m
)
−
1
呢
即
x≡b⋅A−1 mod(mo)
x
≡
b
⋅
A
−
1
m
o
d
(
m
o
)
这样我们一开始只需要求出A的逆元,即最初的
(am)−1
(
a
m
)
−
1
即可
之后for循环枚举中i每增加1,就b就多乘一次逆元即可,就不用每次用扩展欧几里徳解同于式了。
code:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
#include <set>
#include <cmath>
typedef long long ll;
using namespace std;
const int maxn = 510;
const ll mod = 100000007;
int n, m, k, b, r, x[maxn], y[maxn];
set<pair<int, int> > best;
ll mul_mod(ll a, ll b) {
return a * b % mod;
}
ll pow_mod(ll a, ll p) {
ll tmp = 1;
while (p) {
if (p & 1)
tmp = tmp * a % mod;
p >>= 1;
a = a * a % mod;
}
return tmp;
}
void gcd(ll a, ll b, ll &d, ll &x, ll &y) {
if (!b) {
d = a;
x = 1;
y = 0;
}
else {
gcd(b, a%b, d, y, x);
y -= x*(a/b);
}
}
ll inv(ll a) {
ll d, x, y;
gcd(a, mod, d, x, y);
return d == 1 ? (x+mod) % mod : -1;
}
int log_mod(int a, int b) {
int m, v, e = 1, i;
m = (int)sqrt(mod+0.5);
v = inv(pow_mod(a, m));
map<int, int> x;
x[1] = 0;
for (int i = 1; i < m; i++) {
e = mul_mod(e, a);
if (!x.count(e))
x[e] = i;
}
for (int i = 0; i < m; i++) {
if (x.count(b))
return i*m + x[b];
b = mul_mod(b, v);
}
return -1;
}
int count() {
int c = 0;
for (int i = 0; i < b; i++)
if (x[i] != m && !best.count(make_pair(x[i]+1, y[i])))
c++;//不能涂色的格子下面一个格子有k种
c += n;//第一行如果都能涂色有k种
for (int i = 0; i < b; i++)
if (x[i] == 1)
c--;//除去第一行中不能涂色的
return mul_mod(pow_mod(k, c), pow_mod(k-1, (ll)m*n-b-c));//固定部分总的减去不能涂的,减去k种的就是k-1种的
}
int doit() {
int cnt = count();
if (cnt == r)
return m;
int c = 0;
for (int i = 0; i < b; i++)
if (x[i] == m)
c++;
m++;
cnt = mul_mod(cnt, pow_mod(k, c));
cnt = mul_mod(cnt, pow_mod(k-1, n-c));
if (cnt == r)
return m;
return log_mod(pow_mod(k-1, n), mul_mod(r, inv(cnt))) + m;
}
int main() {
int t, cas = 1;
scanf("%d", &t);
while (t--) {
scanf("%d%d%d%d", &n, &k, &b, &r);
best.clear();
m = 1;
for (int i = 0; i < b; i++) {
scanf("%d%d", &x[i], &y[i]);
if (x[i] > m)
m = x[i];
best.insert(make_pair(x[i], y[i]));
}
printf("Case %d: %d\n", cas++, doit());
}
return 0;
}