2024牛客暑期多校训练营2

A. Floor Tiles

如果是从地板边缘进去的曲线一定组不成一个圆,所以会从另一块地板的边缘离开,又因为每块地板都有 2 2 2 条曲线, n m nm nm 规模的地板总共有 2 ( n + m ) 2(n+m) 2(n+m) 个边缘曲线,所以这些边缘曲线可以组成共 n + m n+m n+m 个曲线,这些曲线都是从边缘进入从边缘离开的。

接下来考虑在地板内部组成的环,直接考虑可以组成最多环的情况。

易证得构造形如 A   B B   A \begin{array}{c} {A\ B} \\ {B\ A} \end{array} A BB A这样的单位圆是最优的。

如果在最优情况下左上角是 A A A 类型的地板,构造出来的图形就如下图所示,这种情况最多存在 ⌊ ( n − 1 ) ( m − 1 ) + 1 2 ⌋ \lfloor \frac{(n-1)(m-1)+1}2 \rfloor 2(n1)(m1)+1 个环

在这里插入图片描述

如果左上角是 B B B 类型的地板,构造出来的图形就如下图所示,这种情况最多存在 ⌊ ( n − 1 ) ( m − 1 ) 2 ⌋ \lfloor \frac{(n-1)(m-1)}2 \rfloor 2(n1)(m1) 个环

在这里插入图片描述

(由于比较懒所以直接偷了出题人画的图)

根据给定条件,构造出一个左上角为 A A A 类型或者 B B B 类型地板的 A B AB AB 交替的初始图。如果给定的曲线数量 k k k不在最少曲线数量和最大曲线数量范围中时,输出 N o No No ,否则从上往下,从左往右开始覆盖构造出来的图,每次全部填写 A A A 或者 B B B 来覆盖掉一个构造出来的圆,直到曲线数量满足条件为止,时间复杂度 O ( n m ) O(nm) O(nm)

#include <bits/stdc++.h>
#define int long long
#define YES "YES"
#define NO "NO"
#define all(a) a.begin(), a.end()
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
const double eps = 1e-9;
const int N = 1e6 + 100;
const int INF = 1e18 + 100;
const int mod = 998244353;
const int base = 23333;
char a[1005][1005];
void solve()
{
	int n, m, k;
	cin >> n >> m >> k;
	int x, y, f = 1;
	char c;
	cin >> x >> y >> c;
	x = (x + y) % 2;
	int mn = n + m, mx = n + m + (n - 1) * (m - 1) / 2;
	if(x == 0 && c == 'A' || x == 1 && c == 'B')
	{
		f = 0;
		mx =  n + m + ((n - 1) * (m - 1) + 1) / 2;
	}
	if(k < mn || k > mx) 
	{
		cout << "No" << '\n';
		return;
	}
	for(int i = 1;i <= n;i++)
	{
		for(int j = 1;j <= m;j++)
		{
			if((i + j) % 2 == f) a[i][j] = 'A';
			else a[i][j] = 'B';
		}
	}
	for(int i = 1;i < n;i++)
	{
		for(int j = 1;j < m;j++)
		{
			if(mx > k && (i + j) % 2 == f)
			{
				mx--;
				a[i][j] = a[i][j + 1] = c;
			}
		}
	}
	cout << "Yes" << '\n';
	for(int i = 1;i <= n;i++)
	{
		for(int j = 1;j <= m;j++) cout << a[i][j];
		cout << '\n';
	}
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	cin >> t;
	while (t--)
	solve();
	return 0;
}

B. MST

由于查询点数量有限,且边的总数有限,所以我们将数据进行分块处理。

当询问点数量 ≤ n \leq \sqrt{n} n 时用两个 f o r for for 循环暴力查找 m a p map map 中是否存有这两个点的边,如果有加入当前的边集。当询问点数量 > n > \sqrt{n} >n 时访问所有存在的 m m m 条边,查询是否有这条边对应的两个点,如果有加入当前的边集。

处理完边集后用Kruskal最小生成树算法即可,时间复杂度 O ( n l o g n + m n ) O(nlogn+m\sqrt{n}) O(nlogn+mn )

#include <bits/stdc++.h>
#define int long long
#define YES "YES"
#define NO "NO"
#define all(a) a.begin(), a.end()
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
const double eps = 1e-9;
const int N = 1e6 + 100;
const int INF = 1e18 + 100;
const int mod = 998244353;
const int base = 23333;
struct node
{
    int u, v, w;
};
vector<node> e, ne;
int fa[N], b[N];
int find(int x) {
    while (x != fa[x]) {
        x = fa[x] = fa[fa[x]];
    }
    return x;
}
bool merge(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y) {
        return false;
    }
    fa[y] = x;
    return true;
}
bool cmp(node a, node b)
{
    return a.w < b.w;
}
void solve()
{
    map<pii, int> mp;
    int n, m, q;
    cin >> n >> m >> q;
    int T = sqrt(n);
    for(int i = 1;i <= m;i++)
    {
        int u, v, w;
        cin >> u >> v >> w;
        if(u > v) swap(u, v);
        e.push_back({u, v, w});
        mp[{u, v}] = w;
    }
    while(q--)
    {
        int k;
        cin >> k;
        for(int i = 1;i <= k;i++)
        {
            cin >> b[i];
            fa[b[i]] = b[i];
        }
        if(k <= T)
        {
            for(int i = 1;i <= k;i++)
            {
                for(int j = i + 1;j <= k;j++)
                {
                    int u = b[i], v = b[j];
                    if(u > v) swap(u, v);
                    if(mp.count({u, v})) ne.push_back({u, v, mp[{u, v}]});
                }
            }
        }
        else
        {
            for(auto [u, v, w] : e)
            {
                if(fa[u] && fa[v]) ne.push_back({u, v, w});
            }
        }
        sort(all(ne), cmp);
        int ans = 0, cnt = 0;
        for(auto [u, v, w] : ne)
        {
            if(merge(u, v))
            {
                cnt++;
                ans += w;
            }
        }
        if(cnt == k - 1) cout << ans << '\n';
        else cout << -1 << '\n';
        for(int i = 1;i <= k;i++) fa[b[i]] = 0;
        ne.clear();
    }
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	//cin >> t;
	while (t--)
	solve();
	return 0;
}

C. Red Walking on Grid

考虑 d p [ i ] [ j ] dp[i][j] dp[i][j] i i i 为当前的横坐标 j j j 为当前的纵坐标,如果当前行是 R R R 则可以从 d p [ i ] [ j − 1 ] dp[i][j - 1] dp[i][j1] 转移而来,即 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + 1 dp[i][j]=dp[i-1][j]+1 dp[i][j]=dp[i1][j]+1 ,如果当前行和另一行均为 R R R ,则可以从另一行转移而来,即 d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ 1 − j ] + 2 ) dp[i][j] = max(dp[i][j],dp[i - 1][1-j]+2) dp[i][j]=max(dp[i][j],dp[i1][1j]+2) ,每次计算完 d p dp dp 值后 a n s ans ans m a x ( a n s , m a x ( d p [ i ] [ 0 ] , d p [ i ] [ 1 ] ) ) max(ans,max(dp[i][0],dp[i][1])) max(ans,max(dp[i][0],dp[i][1])) ,最后的 a n s − 1 ans-1 ans1 即为所求,时间复杂度 O ( n ) O(n) O(n)

#include <bits/stdc++.h>
#define int long long
#define YES "YES"
#define NO "NO"
#define all(a) a.begin(), a.end()
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
const double eps = 1e-9;
const int N = 1e6+ 100;
const int INF = 1e18 + 100;
const int mod = 998244353;
const int base = 23333;
int dp[N][2];
void solve()
{
    int n;
    cin >> n;
    string a, b;
    cin >> a;
    cin >> b;
    a = " " + a;
    b = " " + b;
    int ans = 1;
    for(int i = 1;i <= n;i++)
    {
        if(a[i] == 'R') dp[i][0] = dp[i - 1][0] + 1;
        if(b[i] == 'R') dp[i][1] = dp[i - 1][1] + 1;
        if(a[i] == 'R' && b[i] == 'R')
        {
            dp[i][0] = max(dp[i][0], dp[i - 1][1] + 2);
            dp[i][1] = max(dp[i][1], dp[i - 1][0] + 2);
        }
        ans = max(ans, max(dp[i][0], dp[i][1]));
    }
    cout << ans - 1 << '\n';
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	//cin >> t;
	while (t--)
	solve();
	return 0;
}

E. GCD VS XOR

n n n 的二进制的最低位 1 1 1 改成 0 0 0 后的数即为所求,如果 n n n 2 2 2 的整数次幂则无解,时间复杂度 O ( 64 t ) O(64t) O(64t)

#include <bits/stdc++.h>
#define int long long
#define YES "YES"
#define NO "NO"
#define all(a) a.begin(), a.end()
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
const double eps = 1e-9;
const int N = 1e6 + 10;
const int INF = 1e18 + 100;
const int mod = 998244353;
const int base = 23333;
void solve()
{
	int n;
	cin >> n;
	int cnt = 0, ans = INF;
	for(int i = 0;i <= 63;i++)
	{
		if((1LL << i) & n)
		{
			cnt++;
			ans = min(ans, i);
		}
	}
	if(cnt == 1) cout << -1 << '\n';
	else cout << n - (1LL << ans) << '\n';
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	cin >> t;
	while (t--)
	solve();
	return 0;
}

H. Instructions Substring

特判:如果目标坐标为 ( 0 , 0 ) (0,0) (0,0) 直接输出 n ∗ ( n + 1 ) / 2 n*(n +1)/2 n(n+1)/2

设当前位置 n x = 0 nx=0 nx=0 n y = 0 ny=0 ny=0 ,目标位置为 ( x , y ) (x,y) (x,y) 从前往后遍历字符串,遇到相应的指令改变 n x nx nx n y ny ny 的值,并将 n x nx nx n y ny ny 的值存在 m a p map map 中,同时进行查询,如果 m a p map map 中存在元素 n x − x , n y − y {nx-x,ny-y} nxxnyy,说明前面存在若干个左端点,使得运行完这些左端点指令到当前指令后刚好可以到达目标位置,所以以这些左端点为左端点,后面的所有指令子串都一定能经过目标位置,查询完后将 m p [ n x − x , n y − y ] mp[{nx-x,ny-y}] mp[nxx,nyy] 的值置为 0 0 0,累加后的答案即为所求,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

#include <bits/stdc++.h>
#define int long long
#define YES "YES"
#define NO "NO"
#define all(a) a.begin(), a.end()
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
const double eps = 1e-9;
const int N = 1e6 + 10;
const int INF = 1e18 + 100;
const int mod = 998244353;
const int base = 23333;
void solve()
{
	int n, x, y;
	cin >> n >> x >> y;
	string s;
	cin >> s;
	if(x == 0 && y == 0)
	{
		cout << n * (n + 1) / 2 << '\n';
		return;
	}
	int nx = 0, ny = 0, ans = 0;
	map<pii, int> mp;
	mp[{0, 0}] = 1;
	for(int i = 0;i < n;i++)
	{
		if(s[i] == 'W') ny++;
		else if(s[i] == 'S') ny--;
		else if(s[i] == 'A') nx--;
		else nx++;
		mp[{nx, ny}]++;
		//cout << nx << " " << ny << '\n';
		if(mp.count({nx - x, ny - y}))
		{
			ans = ans + mp[{nx - x, ny - y}] * (n - i);
			mp[{nx - x, ny - y}] = 0;
		}
		//cout << i << " " << ans << '\n';
	}
	cout << ans << '\n';
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	//cin >> t;
	while (t--)
	solve();
	return 0;
}

I. Red Playing Cards

f ( i ) f(i) f(i) 表示拿掉 ( l [ i ] , r [ i ] ) (l[i],r[i]) (l[i],r[i]) 区间后能获得的最大分数,如果在初始序列的左右两端都加一个 0 0 0 后是不影响最后的答案的,所以 f ( 0 ) f(0) f(0) 即为所求。

对于每个 i i i ,遍历 ( l [ i ] , r [ i ] ) (l[i],r[i]) (l[i],r[i]) ,如果拿走 ( l [ i ] , r [ i ] ) (l[i],r[i]) (l[i],r[i]) 区间,对于每个下标 j j j 来说都为 f ( i ) f(i) f(i) 贡献了 i i i ,所以可以得到转移方程 d p [ j + 1 ] = m a x ( d p [ j + 1 ] , d p [ j ] + i ) dp[j+1]=max(dp[j+1],dp[j]+i) dp[j+1]=max(dp[j+1],dp[j]+i) 。如果当前下标 j j j 满足 l [ a [ j ] ] = = j & & r [ a [ j ] ] < r [ i ] l[a[j]]==j\&\&r[a[j]]<r[i] l[a[j]]==j&&r[a[j]]<r[i] ,此时就有两种选择,一种是先拿走 ( l [ a [ j ] ] , r [ a [ j ] ] ) (l[a[j]],r[a[j]]) (l[a[j]],r[a[j]]) 区间,另一种是保留 ( l [ a [ j ] ] , r [ a [ j ] ] ) (l[a[j]],r[a[j]]) (l[a[j]],r[a[j]]) 区间,由此就能得出转移方程 d p [ r [ a [ j ] ] + 1 ] = m a x ( d p [ r [ a [ j ] ] + 1 ] , d p [ j ] + f [ a [ j ] ] ) dp[r[a[j]] + 1] = max(dp[r[a[j]] + 1], dp[j] + f[a[j]]) dp[r[a[j]]+1]=max(dp[r[a[j]]+1],dp[j]+f[a[j]]),由于遍历的时候没有计算 l [ i ] l[i] l[i] r [ i ] r[i] r[i] 两个端点,所以 f [ i ] f[i] f[i] 要加上 2 ∗ i 2*i 2i ,即 f [ i ] = 2 ∗ i + d p [ r [ i ] ] f[i]=2*i+dp[r[i]] f[i]=2i+dp[r[i]],最终输出 f [ 0 ] f[0] f[0] 即可,时间复杂度 O ( n 2 ) O(n^2) O(n2)

#include <bits/stdc++.h>
#define int long long
#define YES "YES"
#define NO "NO"
#define all(a) a.begin(), a.end()
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
const double eps = 1e-9;
const int N = 1e6+ 100;
const int INF = 1e18 + 100;
const int mod = 998244353;
const int base = 23333;
int a[N], l[N], r[N];
void solve()
{
    int n;
    cin >> n;
    for(int i = 1;i <= 2 * n;i++)
    {
        cin >> a[i];
        r[a[i]] = i;
    }
    for(int i = 2 * n;i >= 1;i--) l[a[i]] = i;
    l[0] = 0;
    r[0] = 2 * n + 1;
    vector<int> f(n + 5, 0);
    for(int i = n;i >= 0;i--)
    {
        vector<int> dp(2 * n + 5, 0);
        for(int j = l[i] + 1;j < r[i];j++)
        {
            dp[j + 1] = max(dp[j + 1], dp[j] + i);
            if(j == l[a[j]] && r[a[j]] < r[i]) dp[r[a[j]] + 1] = max(dp[r[a[j]] + 1], dp[j] + f[a[j]]);
        }
        f[i] = 2 * i + dp[r[i]];
    }
    cout << f[0] << '\n';
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	//cin >> t;
	while (t--)
	solve();
	return 0;
}
  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值