感谢https://blog.csdn.net/calabash_boy/article/details/81180001的分享
题意:给你一个n*m的矩阵,每个格子有自己的颜色和权值,现在要你选择一个子矩阵,假设子矩阵的大小是p*q的,子矩阵选择的条件是,将子矩阵无限的平移复制粘贴,原来的n*m的矩阵是复制粘贴之后矩阵的子矩阵,选择子矩阵有一个花费,花费是原矩阵所有的大小为p*q的子矩阵中选择一个最大值x,花费就是x*p*q。n*m<=1e6。
做法:将此题分成两步来考虑,首先我们要找到我们的p*q的子矩阵,该子矩阵一定是原矩阵的一个循环节,不然复制粘贴之后无法构成原矩阵,我们要找最小的循环节,这样可以在后面一步中保证花费最小。之后就是线性时间复杂度内找到所有p*q矩阵中的最小值。
第一步,找循环节。利用KMP算法。首先我们应该知道的是:假设字符串的长度是l,那么最小的循环节的大小就是len-next[len] ,除此以外还有len-next[next[len]],等等,于是我们对于每一行每一列都将有可能的循环节大小都统计出来,当出现的次数是n(或者m)的时候一定是可以的,于是这样就找到了循环节的大小。
第二步,在大小n*m的矩阵中p*q的子矩阵中最大值最小,于是我们有限队列预处理一下就好了,先预处理出来每一行连续的q个的最大值,再找到连续的p行中最大值的最大。
代码如下:
#include<bits/stdc++.h> using namespace std; const int maxn = 1e6+100; vector<int>tmp; struct KMPer { int next[maxn]; int len; void clear() { len = 0; next[0] = next[1] = 0; } void NEXT(char ss[]) { // cout<<ss+1<<endl; len = strlen(ss+1); //别忘了+1 下角标从0开始的 错误1 for(int i=2; i<=len; i++) { next[i] = next[i-1]; while(next[i] && ss[i]!=ss[next[i]+1]) next[i] = next[next[i]]; next[i] += (ss[i]==ss[next[i]+1]); } // for(int i=1; i<=len; i++) // { // printf("%d..%d..+++ " , i , next[i]); // } // printf(" "); } void xun_huan() { int now = len; while( now ) { now = next[now]; tmp.push_back(len-now); } } }; KMPer kmper; vector<string> s; vector<vector<int> > a; vector<vector<int> >maxVal; int cnt1[maxn],cnt2[maxn]; int n,m; char S[maxn]; pair<int,int> pq[maxn]; int l,r; int main() { #ifdef ONLINE_JUDGE ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); #endif while( cin>>n>>m ) { ///-------------------初始化-------------- memset(cnt1, 0, sizeof(cnt1)); memset(cnt2, 0, sizeof(cnt2)); s.resize(n+1); maxVal.resize(n+1); a.resize(n+1); ///--------------输入--------------- for (int i=1; i<=n; i++) { cin>>s[i]; a[i].resize(m+1); maxVal[i].resize(m+1); } for (int i=1; i<=n; i++) { for (int j=1; j<=m; j++) { cin>>a[i][j]; } } ///________________横向寻找每一行的 循环节大小--------------- kmper.clear(); for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { S[j] = s[i][j-1]; } S[m+1] = '