2025牛客寒假算法基础集训营2


A. 一起奏响历史之音!

题意:判断7个数里有没有出现4或7.

点击查看代码
void solve() {
	int a[7];
	for (int i = 0; i < 7; ++ i) {
		std::cin >> a[i];
	}

	for (int i = 0; i < 7; ++ i) {
		if (a[i] == 4 || a[i] == 7) {
			std::cout << "NO\n";
			return;
		}
	}

	std::cout << "YES\n";
}

B. 能去你家蹭口饭吃吗

题意:给你一个数组,问最大的满足小于a数组一半数以上的数字是多少。

排序后取中位数减一即可。

点击查看代码
void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}

	std::sort(a.begin(), a.end());

	std::cout << a[n / 2] - 1 << "\n";
}

C. 字符串外串

题意:字符串的可爱度定义为最大的k满足有一个长度为k的字串与一个不完全连续的子序列相等。要求构造一个长度为n可爱度为m的字符串。

n小于m+1显然无解,大于m+26也无解,因为我们至少需要m+1长度来构造,然后因为小写字母只有26个,那么每27个字符必然有一个重复的,那么会导致有一个长度大于m的串也能找到一个相等的子序列。
关于构造方式,可以"abcd..xyz"26个字符串一直填,直到剩余长度小于26,然后前面加上"abcd..xyz"这个串的前缀即可。

点击查看代码
void solve() {
	int n, m;
	std::cin >> n >> m;
	if (n < m + 1 || n > m + 1 + 25) {
		std::cout << "NO\n";
		return;
	}

	std::string t;
	for (int i = 0; i < 26; ++ i) {
		t += (char)('a' + i);
	}

	std::cout << "YES\n";
	std::string s;
	while (s.size() + 26 <= m) {
		s += t;
	}

	s = s + t.substr(0, m - (int)s.size());
	s = t.substr(0, n - (int)s.size()) + s;

	std::cout << s << "\n";
}

D. 字符串里串

题意:字符串的可爱度定义为最大的k满足有一个长度为k的字串与一个不完全连续的子序列相等。给你一个字符串,求它的可爱度。

对于任意长度为K的一个字串,如果它的第一个字符在它的前面也有出现,或者它的最后一个字符在它后面也有出现,这个字符串就是k可爱的。可以直接找第二次出现位置最前的字符和倒数第二次出现位置最后的字符,他们的区间长度就是答案,但赛时没想到这一点,写了个二分。因为如果有长度为k的字串满足,那么k1的也有,可以二分,每次枚举所有mid长度的字串看它的第一个字符在它的前面有没有出现,或者它的最后一个字符在它后面有没有出现,存下来所有字符出现的位置,check可以O(n)

点击查看代码
void solve() {
	int n;
	std::cin >> n;
	std::string s;
	std::cin >> s;
	std::vector<std::vector<int> > pos(26);
	for (int i = 0; i < n; ++ i) {
		pos[s[i] - 'a'].push_back(i);
	}

	auto check = [&](int len) -> bool {
		for (int i = 0; i + len - 1 < n; ++ i) {
			if (i != pos[s[i] - 'a'][0] || i + len - 1 != pos[s[i + len - 1] - 'a'].back()) {
				return true;
			}		
		}

		return false;
	};

	int l = 1, r = n - 1;
	while (l < r) {
		int mid = l + r + 1 >> 1;
		if (check(mid)) {
			l = mid;
		} else {
			r = mid - 1;
		}
	}

	if (!check(l)) {
		std::cout << 0 << "\n";
	} else {
		std::cout << l << "\n";
	}
}

E. 一起走很长的路

题意:给你一个数字,q次询问,每次问一个区间[l,r],对于i[l+1,r]的每个i,是不是都有ai>j=li1aj,你可以每次个一个数加一或者减一,问最后操作多少次才能满足条件。

如果我们给前面的加一,它会使得后面的数更容易满足条件,减一则可能导致本来满足结果小于后面的数了。那么我们应该只进行加一操作。设sumi=j=1iaj,那么意味着对于i[l+1,r],都要满足ai(sumi1suml1)<=0,那么我们维护aisumi1的最大值,就知道最少需要加多少。

点击查看代码
#define ls (u << 1)
#define rs (u << 1 | 1)
#define umid (tr[u].l + tr[u].r >> 1)

struct Node {
	int l, r;
	i64 max;
};

const int N = 2e5 + 5;

i64 a[N], sum[N];

struct SegmentTree {
	std::vector<Node> tr;
	SegmentTree(int _n) {
		tr.assign(_n << 2, {});
		build(1, _n);
	}

	void pushup(int u) {
		tr[u].max = std::max(tr[ls].max, tr[rs].max);
	}

	void build(int l, int r, int u = 1) {
		tr[u] = {l, r};
		if (l == r) {
			tr[u].max = a[l] - sum[l - 1];
			return;
		}

		int mid = l + r >> 1;
		build(l, mid, ls); build(mid + 1, r, rs);
		pushup(u);
	}

	i64 query(int l, int r, int u = 1) {
		if (l <= tr[u].l && tr[u].r <= r) {
			return tr[u].max;
		}

		int mid = umid;
		if (r <= mid) {
			return query(l, r, ls);
		} else if (l > mid) {
			return query(l, r, rs);
		}

		return std::max(query(l, r, ls), query(l, r, rs));
	}
};

void solve() {
	int n, q;
	std::cin >> n >> q;
	for (int i = 1; i <= n; ++ i) {
		std::cin >> a[i];
		sum[i] = sum[i - 1] + a[i];
	}

	SegmentTree tr(n);
	while (q -- ) {
		int l, r;
		std::cin >> l >> r;
		if (l == r) {
			std::cout << 0 << "\n";
		} else {
			i64 max = tr.query(l + 1, r);
			std::cout << std::max(0ll, max + sum[l - 1]) << "\n";
		}
	}
}

F. 起找神秘的数!

题意:求[l,r]内有多少x+y=(x and y)+(x or y)+(x xor y)

我们发现,与运算是取x,y都有的1,异或运算是取x,y这一位恰好一个1的1,而或运算是只要有1就算上,那么(x and y)+(x xor y)=(x or y)。那么我们要求x+y=2(x or y),因为x+y>=2(x or y),发现只有x=y的时候才能满足。

点击查看代码
void solve() {
	i64 l, r;
	std::cin >> l >> r;
	std::cout << r - l + 1 << "\n";
}

G. 一起铸最好的剑!

题意:求|nmx|值最小的x

特判m=1的情况,其他情况模拟即可,因为n最多1e18x最多取到60。

点击查看代码
void solve() {
	i64 n, m;
	std::cin >> n >> m;
	i64 ans = 1, d = std::abs(n - m);
	if (m == 1) {
		std::cout << 1 << "\n";
		return;
	}

	using i128 = __int128;

	auto abs = [&](i128 x) -> i128 {
		return x >= 0 ? x : -x;
	};	

	i128 x = m * m;
	for (int i = 2; x <= n + d; x *= m, ++ i) {
		if (abs(n - x) < d) {
			d = abs(n - x);
			ans = i;
		}
	}

	std::cout << ans << "\n";
}

H. 一起画很大的圆!

题意:在一个矩形的边上找三个点,使得可以让边经过这三个点的圆半径最大。

假设三个点是A,B,C,那么我们要让AB,BC的中垂线角度最大,那么可以选长边最边上的两个点,和短边上距离长边最近的点,这样的角度是最大的。

点击查看代码
void solve() {
	int a, b, c, d;
	std::cin >> a >> b >> c >> d;
	if (b - a >= d - c) {
		std::cout << a << " " << c << "\n";
		std::cout << a + 1 << " " << c << "\n";
		std::cout << b << " " << c + 1 << "\n";
	} else {
		std::cout << a << " " << c << "\n";
		std::cout << a << " " << c + 1 << "\n";
		std::cout << a + 1 << " " << d << "\n";
	}
}

I. 一起看很美的日落!

题意:给你一棵树,每个节点有点权。一个连通块的值定义为连通块里的任意两个点异或和的和。求树上所有连通块值的和。

对于位运算,我们尽可能考虑按位求和。
这题明显是树形dp,那么我们考虑如何计算贡献。记dpu为以u为根的子树中所有连通块的和。cntu记为以u为根的子树中连通块的数量。fu0/1为以u为根的子树中所有连通块的0/1的个数。那么当我们计算第i位时,u枚举到v这棵子树时,可以得dpu=dpu+dpu×cntv+dpv×dpu+2i×(fu0×fv1+fu1×fv0)
其中dpu×cntvu已经计算过的子树贡献乘上v这棵子树有多少个连通块,因为每多一个连通块,之前任何一个连通块里的贡献都要再加一次。dpv×dpu同理。2i×(fu0×fv1+fu1×fv0)表示已经计算过的子树和v这棵子树两者之间增加的贡献,那就是看有多少个1去和0异或和有多少0去和1异或,因为是在计算第i位,所以要乘上2i
fu的计算也是类似的,fu0=fu0+fu0×cntv+fv0×cntufu1=fu1+fu1×cntv+fv1×cntu
cnt的贡献则简单算。cntu=cntu+cntu×cntv

点击查看代码
void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}

	std::vector<std::vector<int> > adj(n);
	for (int i = 1; i < n; ++ i) {
		int u, v;
		std::cin >> u >> v;
		-- u, -- v;
		adj[u].push_back(v);
		adj[v].push_back(u);
	}

    std::vector dp(n, std::array<Z, 30>{});
    std::vector f(n, std::array<std::array<Z, 2>, 30>{});
    std::vector<Z> cnt(n);
    Z ans = 0;
    auto dfs = [&](auto self, int u, int fa) -> void {
        cnt[u] = 1;
        for (int i = 0; i < 30; ++ i) {
            f[u][i][~a[u] >> i & 1] = 0;
            f[u][i][a[u] >> i & 1] = 1;
        }

        for (auto & v : adj[u]) {
            if (v == fa) {
                continue;
            }

            self(self, v, u);
            for (int i = 0; i < 30; ++ i) {
                dp[u][i] += dp[u][i] * cnt[v] + dp[v][i] * cnt[u] + 
                            (f[u][i][0] * f[v][i][1] + f[u][i][1] * f[v][i][0]) * (Z)(1 << i);
                f[u][i][0] += f[u][i][0] * cnt[v] + f[v][i][0] * cnt[u];
                f[u][i][1] += f[u][i][1] * cnt[v] + f[v][i][1] * cnt[u];
            }

            cnt[u] += cnt[u] * cnt[v];
        }

        for (int i = 0; i < 30; ++ i) {
            ans += dp[u][i];
        }
    };

    dfs(dfs, 0, -1);
    std::cout << ans * 2 << "\n";
}

J. 数据时间?

题意:统计某一天的三个时间段有多少人。

string比较每个时间段确定是哪个时间段即可,答案用set存可以去重。

点击查看代码
void solve() {
	int n;
	std::string y, m;
	std::cin >> n >> y >> m;
	if (m.size() == 1) {
		m = "0" + m;
	}
	std::string l[5] = {"07:00:00", "11:00:00", "22:00:00", "00:00:00", "18:00:00"};
	std::string r[5] = {"09:00:00", "13:00:00", "23:59:59", "01:00:00", "20:00:00"};

	std::array<std::set<std::string>, 3> s{};
	for (int i = 0; i < n; ++ i) {
		std::string id, a, b;
		std::cin >> id >> a >> b;
		if (a.substr(0, 4) != y || a.substr(5, 2) != m) {
			continue;
		}
		if ((b >= l[2] && b <= r[2]) || (b >= l[3] && b <= r[3])) {
			s[2].insert(id);
		} else if (b >= l[1] && b <= r[1]) {
			s[1].insert(id);
		} else if ((b >= l[0] && b <= r[0]) || (b >= l[4] && b <= r[4])) {
			s[0].insert(id);
		}
	}

	for (int i = 0; i < 3; ++ i) {
		std::cout << s[i].size() << " \n"[i == 2];
	}
}

K. 可以分开吗?

题意:求每个1的连通块周围0的数量最少的连通块。

dfs搜索即可,可以用set0的位置。

点击查看代码
void solve() {
	int n, m;
	std::cin >> n >> m;
	std::vector<std::string> s(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> s[i];
	}

	const int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
	std::set<std::pair<int, int> > set;
	std::vector vis(n, std::vector<int>(m));
	auto dfs = [&](auto self, int x, int y) -> void {
		if (s[x][y] == '0') {
			set.insert({x, y});
			return;
		}

		vis[x][y] = 1;

		for (int i = 0; i < 4; ++ i) {
			int nx = x + dx[i], ny = y + dy[i];
			if (nx < 0 || nx >= n || ny < 0 || ny >= m || vis[nx][ny]) {
				continue;
			}

			self(self, nx, ny);
		}
	};

	int ans = n * m;
	for (int i = 0; i < n; ++ i) {
		for (int j = 0; j < m; ++ j) {
			if (!vis[i][j] && s[i][j] == '1') {
				set.clear();
				dfs(dfs, i, j);
				ans = std::min(ans, (int)set.size());
			}
		}
	}

	std::cout << ans << "\n";
}

M. 那是我们的影子

题意:有一个3×n的数独,要求每一个3×3的子矩阵中19都恰好出现一次。现在有些位置已经填好数了,问你有多少种可能的答案。

首先判断无解的情况,发现第i行和第i+3行公用两列,那么它们两行填的数必须一样。然后一列里同一个数只能有一个,再就是判断每个3×3的格子是不是合法的。
然后考虑怎么填,根据之前的发现,第ii+3行要填一样的数,那么我们只要枚举前三列怎么填就可以知道后面怎么填了。那么枚举全排列即可,因为9!使比较大的计算量,所以判断排列是否合法要用尽量快的方法。比如可以把每个i列必须填的数字按状压的方式用一个数字表示,那么可以用位运算快速判断是否填了必须填的数。
知道有多少排列合法后,后面每列的填法就是看有多少个问号,乘起来就行了。

点击查看代码
const int mod = 1e9 + 7;

void solve() {
    int n;
    std::cin >> n;
    std::vector<std::string> s(3);
    for (int i = 0; i < 3; ++ i) {
        std::cin >> s[i];
    }

    std::vector<std::set<int> > nums(3);
    for (int j = 0; j < n; ++ j) {
        if (j + 2 < n) {
            std::vector<int> cnt(10);
            for (int i = 0; i < 3; ++ i) {
                for (int k = j; k < j + 3; ++ k) {
                    if (s[i][k] != '?' && ++ cnt[s[i][k] - '0'] > 1) {
                        std::cout << 0 << "\n";
                        return;
                    }
                }
            }
        }

        std::vector<int> cnt(10);
        for (int i = 0; i < 3; ++ i) {
            if (s[i][j] != '?') {
                if (++ cnt[s[i][j] - '0'] > 1) {
                    std::cout << 0 << "\n";
                    return;
                }
                nums[j % 3].insert(s[i][j] - '0');
            }
        }
    }

    if (nums[0].size() > 3 || nums[1].size() > 3 || nums[2].size() > 3) {
        std::cout << 0 << "\n";
        return;
    }

    std::vector<int> st(3);
    for (int i = 0; i < 3; ++ i) {
        for (auto & x : nums[i]) {
            st[i] |= 1 << x;
        }
    }

    std::vector<int> p(9);
    std::iota(p.begin(), p.end(), 1);
    i64 ans = 0;
    do {    
        int flag = 1;
        for (int i = 0; i < 3 && flag; ++ i) {
            for (int j = 0; j < 3; ++ j) {
                if (s[i][j] != '?' && s[i][j] - '0' != p[i * 3 + j]) {
                    flag = 0;
                    break;
                }
            }
        }

        if (!flag) {
            continue;
        }

        for (int i = 0; i < 3; ++ i) {
            int x = (1 << p[i]) + (1 << p[i + 3]) + (1 << p[i + 6]);
            if ((x & st[i]) != st[i]) {
                flag = 0;
                break;
            }
        }

        ans += flag;
    } while (next_permutation(p.begin(), p.end()));


    i64 v[4] = {1, 1, 2, 6};
    for (int i = 3; i < n; ++ i) {
        int cnt = (s[0][i] == '?') + (s[1][i] == '?') + (s[2][i] == '?');
        ans = (ans * v[cnt]) % mod;
    }

    std::cout << ans << "\n";
}
posted @   maburb  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示