• 牛客练习赛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);
        }
    
    
    
    }
    
  • 相关阅读:
    FireFox 中 回把nextSibling为/n 换行时解释为undefinded
    陶哲轩实分析习题18.4.10
    单调可测集的测度运算
    数学分析_Tom Apostol_定理7.47
    Emacs+$\LaTeX$ 帮你写数学文章
    陶哲轩实分析引理18.4.2:半空间是可测集
    陶哲轩实分析习题18.4.10
    陶哲轩实分析引理18.4.2:半空间是可测集
    可测集的性质
    数学分析_Tom Apostol_定理7.47
  • 原文地址:https://www.cnblogs.com/BOZHAO/p/13940892.html
Copyright © 2020-2023  润新知