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种到格子多少个,乘起来就是这部分的总方案数即 ,这个时候我们可以检测一下是否和所给答案相同,相同直接输出答案。
如果不同:
如上分析,max行往下到部分是变化的,首先第max+1行,受到max行中不能涂色到格子的影响,所以我们单独算出来这一行的方案数num,然后乘到之前部分的方案数中,即
,这个时候我们在检查是否符合答案,符合输出。
如果不同:
下面就简单了,每一行都是
种,n是列,每个格子都受上一行影响,所以是k-1种。设
,我们设还需要增加M行,则这M行的格子的总的种类数就是
,再乘上原来那部分种类数我们就得到了同余式
即 (其中 是cnt的逆元)
这样我们到任务就是求M,这就是离散对数取模,需要用BSGS算法
我们继续设
得到
上面我贴了一篇博客网址,其算法步骤如下:
1 设
其中
,
2 先枚举 算出 用hash存下来 并记录对应j
因为 , 已经枚举完并记录下来了
3 枚举i,
可以提前算出来,所以每次枚举i算出
所以每次枚举为们就可以用扩展欧几里徳求同余式方程求出
,然后在已经存好的hash中查找是否存在,存在就说明我们找到了,这样i,j都有了我们就求出了
以上便是这个算法的完整过程
但是我们会发现一个问题,就是枚举i的过程中,每次都需要用扩展欧几里徳求一遍感觉有些麻烦可以简化一些吗?
我们观察一下第三步,看有什么可以改进到地方
第三步中我们需要求
因为第一个式子原式是
枚举i每增加一相当于左边多乘了一个
那么是不是就相当于每次右边多乘一个
呢
即
这样我们一开始只需要求出A的逆元,即最初的
即可
之后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;
}