牛客练习赛1

牛客练习赛1

A矩阵

题意

给出一个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;
}

B树

题意:

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;

}

c 圈圈

题意:

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);
    }



}
原文地址:https://www.cnblogs.com/BOZHAO/p/13940892.html