种种原因,拖了一周,终于动笔!
A.矩阵
思路
二维哈希+二分。二维哈希可以直接套板子,二分正方形的边长。
敲完代码卡九百多毫秒,十次提交有六七次超时但是全是板子一时间不知道怎么修改,银川区域赛打完回来重构代码,发现map改成unordered_map直接到四百多毫秒,我可真是个大S(帅)B(逼)啊!
总而言之,两个知识点:二维矩阵的哈希及匹配,二分查找边长。
CODE
#include<bits/stdc++.h>
using namespace std;
double PI=acos(-1.0);
#define ll long long
#define ull unsigned ll
#define pii pair<ll,ll>
const ll mod=998244353;
ull p1 = 13331,p2 = 233333;
char str[1007][1007];
ull Powh[1007],Powl[1007];
ull Hash[1007][1007];
ull hash1[1007][1007];
void init()
{
Powh[0]=1;
Powl[0]=1;
for(ull i=1;i<=500;i++)
{
Powh[i]=Powh[i-1]*p1;
Powl[i]=Powl[i-1]*p2;
}
}
void make_hash(int n,int m)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
Hash[i][j]=Hash[i][j-1]*p1+str[i][j];
hash1[i][j]=hash1[i-1][j]*p2+Hash[i][j];
}
}
}
ull get_hash(int lx, int ly, int rx, int ry) { // 获取左上角为(lx, ly)右下角是(rx, ry)矩阵的hash值
ull temp = hash1[rx][ry]-hash1[lx-1][ry]*Powl[rx-lx+1]-hash1[rx][ly-1]*Powh[ry-ly+1]+hash1[lx-1][ly-1]*Powl[rx-lx+1]*Powh[ry-ly+1];
return temp;
}
int n,m;
unordered_map<ll, int>vis;
bool check(int num)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(i+num<=n&&j+num<=m)
{
ll cnt=get_hash(i,j,i+num,j+num);
if(vis[cnt])
{
return 1;
}
vis[cnt]++;
}
else
{
break;
}
}
}
return 0;
}
int main()
{
init();
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>(str[i]+1);
}
make_hash(n,m);
int l=0,r=min(n,m);
int mid;
while(l<r)
{
mid=(l+r+1)/2;
if(check(mid))l=mid;
else r=mid-1;
}
cout<<l+1<<endl;
return 0;
}
B.树
思路
已知树的每个结点只连接了一条边,且x结点到y结点的路径上的所有点的颜色都要与x结点和y结点相同,求方案数。
首先,我们控制变量为1种颜色时,求有多少种方案。那么我们通过断掉树的i(0~n-1)条边产生一定的连通块,每个连通块的路径颜色相同。我们可以得到方案数为: C n − 1 i C_{n-1}^{i} Cn−1i 。
扩展到颜色为k种时,有多少种方案。当此时存在j个连通块(即j=i+1)时,颜色全排列,可以得到最终方案数 C n − 1 i C_{n-1}^{i} Cn−1i * A n − 1 j A_{n-1}^{j} An−1j
组合数的话套个板子嘛。
总而言之,两个知识点:断边的组合数,颜色的全排列。
CODE
#include<bits/stdc++.h>
using namespace std;
double PI=acos(-1.0);
#define ll long long
#define pii pair<ll,ll>
const ll mod=1e9+7;
ll fac[2000007];
ll ksm(ll x,ll p){
ll res=1;
while(p){
if(p%2==1) res=res*x%mod;
p/=2;
x=x*x%mod;
}
return res;
}
ll inv(ll a) {
return ksm(a,mod-2)%mod;
}
void solve() {
fac[0] = 1;
for(int i = 1;i < 2000006; i++) {
fac[i] = (fac[i-1]*i)%mod;
}
}
ll comb(ll n,ll k){
if(k>n)return 0;
if(k==1)return n;
return (fac[n]*inv(fac[k])%mod*inv(fac[n-k])%mod);
}
int main()
{
solve();
ios::sync_with_stdio(0);
ll n,k;
cin>>n>>k;
for(int i=1;i<=n-1;i++) scanf("%*d%*d");
ll sum=1;
ll ans=0;
for(int i=0;i<min(n,k);i++)
{
sum=sum*(k-i)%mod;
ans=(ans+comb(n-1,i)*sum%mod)%mod;
}
cout<<ans<<endl;
return 0;
}
C.圈圈
思路
这题确实戳到我知识盲区了,酸爽!好题!
首先,因为每次将队首元素移到队尾,所以这里我们可以直接复制扩展到两倍数组,直接取区间即可。
通过哈希数组后,二分查找LCP,比较公共前缀后一位元素判断字典序。(这里扫盲了!)
同时存在一个优化问题,我们可以预处理加多少次取模后为0的元素位置,这样每次加1后可以只从其中遍历,如存在加1取模后没有0的情况,则字典序不变化输出上一次答案即可。
总的来说,代码虽长就两个知识点,预处理优化,哈希二分查找LCP。
CODE
#include<bits/stdc++.h>
using namespace std;
double PI=acos(-1.0);
#define ll long long
#define ull unsigned long long
#define pii pair<ll,ll>
const ll mod=998244353;
#define base 53331
ull a[100007];
ull has[100007];
ull p[100007];
vector<int>v[50007];
int n,m,k;
int ans=1;
int solve(int x,int y,int num)
{
int l=0,r=n;
while(l<=r)
{
int mid=(l+r)>>1;
if(has[x+mid-1]-has[x-1]*p[mid]==has[y+mid-1]-has[y-1]*p[mid])
l=mid+1;
else
r=mid-1;
}
l--;
if(l==n)
return x;
if((a[x+l]+num)%m<=(a[y+l]+num)%m)
return x;
return y;
}
int main()
{
ios::sync_with_stdio(0);
cin>>n>>m>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
v[m-a[i]].push_back(i);
a[i+n]=a[i];
}
p[0]=1;
for(int i=1;i<=n+n;i++)
{
has[i]=has[i-1]*base+a[i];
p[i]=p[i-1]*base;
}
for(int i=2;i<=n;i++)
{
ans=solve(ans,i,0);
}
cout<<a[ans+k-1]<<endl;
for(int i=1;i<=m-1;i++)
{
if(v[i].size())
{
ans=v[i][0];
for(int j=1;j<v[i].size();j++)
ans=solve(ans,v[i][j],i);
}
cout<<(a[ans+k-1]+i)%m<<endl;
}
return 0;
}