牛客练习赛1
题意:
给出一个n * m的矩阵。让你从中发现一个最大的正方形。使得这样子的正方形在矩阵中出现了至少两次。输出最大正方形的边长。(n <= 500, m <= 500)
题解:考虑暴力
枚举每个矩阵为 (n ^ 3)然后判断两个矩阵是否相等 (n ^ 2)显然复杂度爆炸, 如果将矩阵进行hash 然后o(1)判断两个矩阵是否相等,那么最后的复杂度为 (n ^ 3) , 好像还是不行。
这个时候就要考虑如何优化了。
仔细想一下矩阵中出现两个个长度为(x)的且相同的正方形, 那么一定存在两个长度为(x - 1)相同的正方形, 同样一会出现长度为 (x - 2 ………… , 1)的正方形, 有了这个结论, 那么可以直接二分长度了, 如果存在就向右移动, 否则向左移动。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1000;
const int maxn = 1e3+5;
const int MAXN = 1e3+5;
typedef unsigned long long ull;
ull p = 13331,p1 = 233333;
ull HashA[maxn][maxn];
ull HashA1[maxn][maxn];
ull Powp[maxn];
ull Powp1[maxn];
char str[N][N];
void init() {
Powp[0]=1;
Powp1[0] = 1;
for(int i=1;i<MAXN;i++) {
Powp[i] = Powp[i - 1] * p;
Powp1[i] = Powp1[i - 1] * p1;
}
}
void make_hash(int n,int m){//字符串的范围得i个hash存储每行哈希值
for(int i = 1; i <= n; ++ i) {
for(int j = 1; j <= m; ++ j) {
HashA[i][j] = HashA[i][j - 1] * p + str[i][j];
HashA1[i][j] = HashA1[i - 1][j] * p1 + HashA[i][j];
}
}
}
ull get_hash(int lx, int ly, int rx, int ry) { // 获取左上角为(lx, ly)右下角是(rx, ry)矩阵的hash值
ull temp = HashA1[rx][ry] - HashA1[lx - 1][ry] * Powp1[rx - lx + 1] -HashA1[rx][ly - 1] * Powp[ry - ly + 1]
+ HashA1[lx - 1][ly - 1] * Powp1[rx - lx + 1] * Powp[ry - ly + 1];
return temp;
}
string s[N];
typedef long long ll;
map<ll, int>vis;
int n, m;
bool judge(int i) {
for (int j = 1; j <= n; j++) {
for (int k = 1; k <= m; k++) {
if (j + i <= n && k + i <= m) {
ll cnt = get_hash(j, k, j + i, k + i);
if (vis[cnt]) {
return true;
}
vis[cnt]++;
} else {
break;
}
}
}
return false;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> (str[i] + 1);
}
init();
make_hash(n, m);
int l = 0, r = min(n, m), ans;
while (l <= r) {
int mid = (l + r) / 2;
if (judge(mid)) {
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
cout << ans + 1 << endl;
}
题意:
shy有一颗树,树有n个结点。有k种不同颜色的染料给树染色。一个染色方案是合法的,当且仅当对于所有相同颜色的点对(x,y),x到y的路径上的所有点的颜色都要与x和y相同。请统计方案数。
题解: 考虑组合数学
首先肯定肯定知道, 将树划分成几个联通快, 然后每个联通快涂一个颜色。
假设当前联通快的个数为 (x) 颜色的数量为 (n) 那么当前的答案就是一个全排列, (ans = n * (n - 1) * (n - 2) * ...... (n - x + 1))
那么怎么求一个树可以分成多少个连通块呢?这个问题可以用dp去求, 但是我觉得比较麻烦, 有个数学方法可以直接求。
对于一颗树, 联通快的个数不就是将树删边, 树有 (n - 1)条边, 删一条边那么将出现两个连通块, 删两条边就会出现三个联通快, 同时删一条边的方案数为 (C_{n - 1}^{1}) , 删两条边的方案树为 (C_{n - 1} ^{2}) 以此类推。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4;
typedef long long ll;
const long long mod = 1e9+7;
long long fac[2000006]; // 阶乘表
long long qpow(long long x, long long n) {
long long res = 1;
for (; n; n >>= 1, x = x * x % mod)
if (n & 1) res = res * x % mod;
return res;
}
long long inv(long long a) { // 返回逆元 费马小定理
return qpow(a, mod-2)%mod;
}
void solve() { // 计算阶乘表
fac[0] = 1;
for(int i = 1;i <= 2000006; i++) {
fac[i] = (fac[i-1]*i)%mod;
}
}
long long comb(long long n, long long k) {
if(k > n) return 0;
return (fac[n]*inv(fac[k])%mod * inv(fac[n-k])%mod) % mod;
}
ll n, k;
ll work(ll x) {
ll res = 1;
ll cnt = k;
if (x > cnt) return 0;
for (int i = 1; i <= x; i++) {
res = res * cnt;
res = res % mod;
cnt--;
}
return res;
}
int main() {
solve();
cin >> n >> k;
ll ans = 0;
for (int i = 0; i < n; i++) {
ll cnt = comb(n - 1, i);
ans = (ans + cnt * work(i + 1) % mod) % mod;;
}
cout << ans << endl;
}
题意:
shy有一个队列a[1], a[2],…,a[n]。现在我们不停地把头上的元素放到尾巴上。在这过程中我们会得到n个不同的队列,每个队列都是a[k],a[k+1],…,a[n],a[1],…,a[k-1]的形式。在这些队列中,我们可以找到字典序最小的。
shy无聊的时候会给队列的每个元素加一玩。但是为了使得游戏不这么无聊,shy加一以后会给每个元素模m,这样子字典序最小的序列就会变了,生活就变得有趣。
很显然这样子加m次以后,序列会变成原来的样子。所以现在shy想知道,在他没有加一前,加一时,加二时,….,加m-1时字典序最小的序列的第k(和上面的k没有关系)个元素分别是几。
题解:
先考虑然后找到字典序最小的呢?
最原始的方法就是(n ^ 2)枚举每一个长度为n的字符串然后在比较大小。
这样肯定是不行的, 那有更优的方案吗。
考虑到每个字典序直接的比较一定是前面的元素的都是相等, 然后当前位的元素比较小那么就不需要在继续比较了
是不是可以找到一个最长相等的前缀, 然后在比较下一位。
先选一个起始串, 然后通过二分+哈希找到与起始串最长的前缀, 然后比较下一位, 如果比起始串小, 那么在更新起始串, 继续枚举下一个字符串, 通过 hash+ 二分进行查找, 然后在继续……
这样的复杂度位 枚举为 (o(n)) 判断每个字典序的大小为 (log(n))所以总时间复杂度为(o(n * log(n)))
如果每次都给所以数加1 在模上 m, 总共操作m次, 每次都这么找的话时间复杂度不就是为 (o(n * m * log(n)))
很显然这样肯定过不了, 仔细想一下每次给所有数加1 然后在取模, 是不是只有 变为0的数才对答案产生贡献啊
然后记录有那几个数将在加多少次变为0, 然后在用上面的方案找出字典序的最小的, 因为只操作m次, 所以每个数只会有一次变为0, 所以每个数只进行一次比较,最后的复杂度为(o(m * log(n)))
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 7;
typedef long long ll;
const ll mod = 1e9 + 7;
int n, m, k;
ll fn[N], Hash[N];
ll a[N];
void init() {
fn[0] = 1;
ll d = 27;
for (int i = 1; i <= n; i++) {
Hash[i] = (Hash[i - 1] * d % mod + a[i]) % mod;
fn[i] = fn[i - 1] * d % mod;
}
}
ll get_hash(int l, int r) {
return (Hash[r] - Hash[l - 1] * fn[r - l + 1] % mod + mod) % mod;
}
int work(int x, int y, int v) {
int l = 1, r = n / 2, ans = 0;
while (l <= r) {
int mid = (l + r) / 2;
if (get_hash(x, x + mid - 1) == get_hash(y, y + mid - 1)) {
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
if ((a[x + ans] + v) % m < (a[y + ans] + v) % m) {
return x;
}
return y;
}
vector<int> g[N];
int main() {
scanf("%d %d %d", &n, &m, &k);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
g[a[i]].push_back(i);
a[i + n] = a[i];
}
n += n;
init();
int ans = 1;
for (int i = 2; i <= n / 2; i++) {
ans = work(ans, i, 0);
}
printf("%lld
", a[ans + k - 1]);
for (int i = 1; i < m; i++) {
if (g[m - i].size()) {
for (int j: g[m - i]) {
ans = work(ans, j, i);
}
}
printf("%lld
", (a[ans + k - 1] + i) % m);
}
}