2020牛客暑期多校训练营(第二场) 题解

牛客多校,第二场。

若有公式图片无法正常显示,请使用梯子访问!

A - All with Pairs

  • 题意:
    给定n个字符串,每一个串前缀和其他串的后缀进行匹配,计算匹配的最大长度的平方和。

  • 思路:
    把每个字符串的后缀都hash了存到map里,然后从每个字符串遍历,从前到后,第i个字符串的第j个点字符,我们得到前缀的hash值是x,ans[i][j]=mp[x],然后跑一遍next数组,求出ans[i][next[j]]-ans[i][j],这就是去重.

为什么要使用hash函数而不能直接将后缀存到map:因为直接存入字符串的话在进行匹配时时间复杂度相比数字匹配高很多,在此题会导致TLE。

去重的含义:
比如现在只有一个字符串ababa
从后往前存后缀
mp[a]=1;
mp[ba]=1;
mp[aba]=1;
mp[baba]=1;
mp[ababa]=1;
然后遍历前缀
ans[1]=mp[a]=1;
ans[2]=mp[ab]=0;
ans[3]=mp[aba]=1;
ans[4]=mp[abab]=0;
ans[5]=mp[ababa]=1;
然后从前往后
ans[next]-=ans[i]
最后就只有ans[5]=1了

const ll mod = 998244353;
const ll N = 1e6 + 500;
const ll key = 131;
map<ll, ll> mp;
string s[N];
ll has[N];
ll nex[N];
void Get_Hash(string a)
{
	ll cut = 0;
	ll p = 1;
	for (ll i = a.size() - 1; i >= 0; --i)
	{
		cut += p * (a[i] - 'a' + 1);
		p *= key;
		mp[cut]++;
	}
	return;
}
void kmp(string a, ll n)
{
	nex[0] = -1;
	ll j = 0, k = -1;
	while (j < n)
	{
		if (k == -1 || a[k] == a[j])
			nex[++j] = ++k;
		else
			k = nex[k];
	}
}
int main()
{
	ll n;
	cin >> n;
	for (ll i = 1; i <= n; ++i)
	{
		cin >> s[i];
		Get_Hash(s[i]);
	}
	ll ans = 0;
	for (ll i = 1; i <= n; ++i)
	{
		ll cut = 0;
		ll len = s[i].size();
		for (ll j = 0; j < len; ++j)
		{
			cut = cut * key + (s[i][j] - 'a' + 1);
			has[j + 1] = mp[cut];
		}
		kmp(s[i], len);
		for (ll j = 1; j <= len; ++j)
			has[nex[j]] -= has[j];
		for (ll j = 1; j <= len; ++j)
			ans = (ans + 1ll * has[j] * j * j % mod) % mod;
	}
	cout << ans;
	return 0;
}

B - Boundary

  • 题意:

给定n个点,让更多的点落在同一个经过原点(0,0)的圆上,求出最多的点数。

  • 思路:

根据三个点构成一个圆,即每次选取两个点,加上原点。然后根据这三个点推出该圆圆心的坐标,在存入map中,因为题目保证所有点的坐标不重复,那么圆心坐标相同的三点即在同一个圆上。

根据三点求圆心的方法:画出另外两点与圆心直线的中垂线,两条中垂线的交点即为圆心。其中 (x1, y1) (x2, y2)为另外两点的坐标。可推得公式如下:

pair<int,int> a[2005];

map<pair<double,double>,int> p;

int main() 
{
	IOS;
	int n;
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>a[i].first>>a[i].second;
	int ans=0;
	for(int i=0;i<n;i++)
	{
		/* 
		   开始时清空map,
		   因为假设 (a,b,0),(a,c,0) 圆心相同,
		   则一定有 (a,b,0),(a,c,0),(b,c,0) 圆心相同。
		*/
		p.clear();
		for(int j=i+1;j<n;j++)
		{
			double x1=a[i].first,y1=a[i].second,x2=a[j].first,y2=a[j].second;
			if(x1*y2==x2*y1) //除数不能为0 
			continue;
			//求出圆心坐标
			double x=(y1*(x2*x2+y2*y2)-y2*(x1*x1+y1*y1))/(y1*x2-y2*x1);
			double y=(x1*(x2*x2+y2*y2)-x2*(x1*x1+y1*y1))/(y2*x1-y1*x2);
			p[make_pair(x,y)]++;
			ans=max(ans,p[make_pair(x,y)]);
		}
	}
	cout<<ans+1;//原点(0,0)没算,需+1
	return 0;
}

C - Cover the Tree

  • 题意:
    给定一个无根树,选择树中所有边至少被一条链覆盖的最小链数。print any of answer.

  • 思路:
    寻找不相邻的叶子节点,成对匹配输出。我是通过第i个叶子节点需要和第cnt/2+i个叶子节点相匹配进行的输出, 判断出入度=1即为叶子节点。

int tree[200005], ans[200005];

int main()
{
    IOS;
    int n;
    cin >> n;
    for (int i = 1; i <= n - 1; i++)
    {
        int x, y;
        cin >> x >> y;
        tree[x]++, tree[y]++;//统计出入度
    }
    int cnt = 0;
    for (int i = 1; i <= n; i++)
    {
        if (tree[i] == 1)//叶子节点
        {
            cnt++;
            ans[cnt] = i;
        }
    }
    cout << (cnt + 1) / 2 << endl;
    if (cnt & 1) //奇数时最后一个点处理不到,需特判
    {
        for (int i = 1; i <= (cnt) / 2; i++)
            cout << ans[i] << " " << ans[i + (cnt - 1) / 2] << endl;
        cout << ans[1] << " " << ans[cnt] << endl;
    }
    else
    {
        for (int i = 1; i <= cnt / 2; i++)
            cout << ans[i] << " " << ans[i + cnt / 2] << endl;
    }
    return 0;
}

F - Fake Maxpooling

  • 题意:
    给定大小为n×m的矩阵和整数k,其中A {i,j} =lcm(i,j),是i和j的最小公倍数。找到所有大小为k×k的子矩阵中的最大值之和。

  • 思路:这个题是队友暴力调滑窗板子过的,后来发现其他队伍的一个做法,开始以为思路很简单,深挖了一下发现这个题有DP内味了!好题!

    1. 当k=1时,其每一个k*k的矩形的最大值都是一个数,直接求就行。
    2. 当k>=2时,设定每一个k*k的矩形,其最大值一定是在该矩形的右下角。那么对于这个矩形的最大值来说,可以由左边和上面的相邻矩形的最大值转移过来,如下图:

      然后这个转移过来的最大值再与当前应有的公倍数判断下取最大,即完成状态转移。
int m, n, k;
int a[5001][5001];
int lcm(int a, int b) ///最小公倍数
{
    return (a * b) / __gcd(a, b);
}
int main()
{
    for (int i = 1; i <= 5000; i++)
        for (int j = 1; j <= 5000; j++)
            a[i][j] = lcm(i, j);
    cin >> m >> n >> k;
    ll s = 0;
    if (k == 1)
    {
        for (int i = k; i <= m; i++)
            for (int j = k; j <= n; j++)
                s += a[i][j]; ///从a[k][k]开始取值
    }
    else
    {
        for (int i = k; i <= m; i++)
            for (int j = k; j <= n; j++)
            {

                a[i][j] = max(max(a[i - 1][j], a[i][j - 1]), a[i][j]);
                s += a[i][j]; ///从a[k][k]开始取值
            }
    }
    cout << s << endl;
    return 0;
}

你可能感兴趣的:(字符串,哈希表,动态规划,算法,hash)