牛客练习赛1

种种原因,拖了一周,终于动笔!

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} Cn1i

扩展到颜色为k种时,有多少种方案。当此时存在j个连通块(即j=i+1)时,颜色全排列,可以得到最终方案数 C n − 1 i C_{n-1}^{i} Cn1i * A n − 1 j A_{n-1}^{j} An1j

组合数的话套个板子嘛。

总而言之,两个知识点:断边的组合数,颜色的全排列。

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

第十页

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值