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

题目链接:2025牛客寒假算法基础集训营2

总结:排名:113 10题 803罚时

  • 分层图最短路

A. 一起奏响历史之音!

tag:语法

void solve(){
set<int> st;
st.insert(1);
st.insert(2);
st.insert(3);
st.insert(5);
st.insert(6);
for (int i = 0; i < 7; i ++){
int x;
cin >> x;
if (st.find(x) == st.end()){
cout << "NO\n";
return;
}
}
cout << "YES\n";
}

B. 能去你家蹭口饭吃吗

tag:中位数

void solve(){
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i ++){
cin >> a[i];
}
sort(a.begin(), a.end());
cout << a[n / 2] - 1 << endl;
}

C. 字符串外串

tag:构造

Description:给定一个n和m,构造一个长度为n,可爱度为m的字符串。如果不能输出-1。

Solution:从左往右构造先构造长度为m的串,然后让第m个字母在末尾出现,那么剩余n - m个字母不能出现两次,因此n - m <= 26。

  • 但是为了避免从右往左看可爱度过大,我们需要周期性的构造。
void solve(){
int n, m;
cin >> n >> m;
if (n == m || n > m + 26) {
cout << "NO\n";
}
cout << "YES\n";
int now = -1;
for (int i = 0; i < m; i++){
now = (now + 1) % 26;
char c = 'a' + now;
cout << c;
}
char last = 'a' + now;
for (int i = m; i + 1 < n; i++){
now = (now + 1) % 26;
char c = 'a' + now;
cout << c;
}
cout << last;
cout << "\n";
}

D. 字符串里串

tag:思维

Description:定义可爱度为一个最大的整数k,使得存在长度为a的连续子串、长度为b的不连续子序列,满足a == b。给定一个字符串,求该字符串的可爱度。不连续子序列至少由两段不相邻的非空子串构成。

Solution:仔细观察一下,在确定a之后,b最好的选择是在a第一个字符前面找一个和a[1]一样的字符或者在a的最后一个字符后面找一个和a[n]一样的字符。

  • 那么就变成了找每一个字符出现两次时,串的最大长度。
void solve(){
int n;
cin >> n;
string s;
cin >> s;
s = '$' + s;
set<int> st;
int ans = 0;
for (int i = 1; i <= n; i ++){ // 从前往后
if (st.find(s[i]) != st.end()){
ans = max(ans, n - i + 1);
}
st.insert(s[i]);
}
st.clear();
for (int i = n; i >= 1; i --){ // 从后往前
if (st.find(s[i]) != st.end()){
ans = max(ans, i);
}
st.insert(s[i]);
}
if (ans == 1) // b的长度至少为2
ans = 0;
cout << ans << endl;
}

E. 一起走很长的路!

tag:前缀和,st表

Description:给定一个长度为n的数组,给定k次询问,每次询问给定l,r需要给出操作次数,每次操作可以在一个位置进行+1或者-1操作。

  • 每次操作时:推倒a[l],如果a[l] >= a[l + 1],则推倒a[l + 1],如果a[l] + a[l + 1] >= a[l + 2],则推倒a[l + 2]...,需要给出最少操作多少次能推倒给定区间的所有牌。

Solution:显然+1操作比-1操作好,显然将+1操作放在最前面好。

  • 一个数想要被推到需要前缀和大于该数。考虑记录每个数减去其前缀和,那么需要的操作等于区间最大值再加上一段前缀和(该段前缀和并不影响区间最大值)。
int st[N][21];
int n, k;
void init() {
for (int j = 1; j <= 20; j ++){
for (int i = 1; i + (1 << j) - 1 <= n; i ++) {
st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
}
}
int query(int l, int r){
int k = log2(r - l + 1);
return max(st[l][k], st[r - (1 << k) + 1][k]);
}
void solve(){
cin >> n >> k;
vector<int> a(n + 1), s(n + 1);
for (int i = 1; i <= n; i ++) {
cin >> a[i];
s[i] = s[i - 1] + a[i];
st[i][0] = a[i] - s[i - 1];
}
init();
while (k --){
int l, r;
cin >> l >> r;
if (l == r) {
cout << 0 << endl;
continue;
}
else{ // 最大值询问从l + 1开始,因为a[l]一开始就被推倒
cout << max(0ll, s[l - 1] + query(l + 1, r)) << endl;
}
}
}

F. 一起找神秘的数!

tag:打表

Solution:打表看x + y = (x or y) + (x and y)+ (x xor y)的数的性质,发现当x == y时,才成立。

void solve(){
int l, r;
cin >> l >> r;
cout << r - l + 1 << endl;
for (int i = 0; i < 1000; i ++)
for (int j = i; j < 1000; j ++){
if (i + j == (i | j) + (i & j) + (i ^ j)){
debug(i, j);
}
}
}

G. 一起铸最好的剑!

tag:模拟

void solve(){
int n, m;
cin >> n >> m;
int t = m;
int ans = 1;
int mi = abs(n - m);
while (1){
if (mi > abs(n - m * t)){
ans ++;
m *= t;
mi = abs(n - m);
}
else
break;
}
cout << ans << endl;
}

H. 一起画很大的圆!

tag:数学

Description:给定一个矩形区域,在其中找出三个整数点,使得过这三个点画出的圆的半斤最大。

Solution:三个不共线的点可以确定一个圆,如果三点越接近一条直线,这个圆最大。

  • 猜一下一个点在边界,那么另外两个点就很好猜了。在长边上选一个与边界最近的点,在另一个条边上选一个离长边最近的点。
void solve(){
int a, b, c, d;
cin >> a >> b >> c >> d;
if (b - a > d - c){
cout << a << " " << d << endl;
cout << a + 1 << " " << d << endl;
cout << b << " " << d - 1 << endl;
}
else{
cout << a << " " << d << endl;
cout << a << " " << d - 1 << endl;
cout << a + 1 << " " << c << endl;
}
}

I. 一起看很美的日落!

tag:树形DP

Description:给定一颗n个节点的树,每个节点的权值为ai,定义一个连通块的权值为两两节点的权值异或和,求全部连通块的异或和。

Solution:求异或和可以转化为按位求每一个01的个数

  • dp[u]:包含u这个节点的子树的所有答案。
  • g[u]:包含u这个节点的子树的连通块个数。
  • f[u][0/1]:保护u这个节点的0的贡献次数和1的贡献次数。
  • g[u] = g[u] + g[u] * g[v]
  • f[u][0/1] = f[u][0/1] + f[u][0/1] * g[v] + f[v][0/1] * g[u]
  • dp[u] = dp[u] + dp[u] * g[v] + dp[v] * g[u] + f[u][0/1] * f[v][1/0] + f[v][0/1] * f[u][1/0]
const int N = 1e5 + 10;
int n;
int dp[N], g[N];
int f[N][31][2];
void solve(){
cin >> n;
vector e(n + 1, vector<int>());
vector<int> a(n + 1);
for (int i = 0; i < n; i ++) {
cin >> a[i + 1];
}
for (int i = 0; i < n - 1; i ++) {
int u, v;
cin >> u >> v;
e[u].pb(v), e[v].pb(u);
}
int ans = 0;
function<void(int, int)> dfs = [&](int u, int fa) {
for (int i = 0; i < 31; i ++) {
if ((a[u] >> i) & 1) {
f[u][i][1] = 1;
}
else
f[u][i][0] = 1;
}
g[u] = 1;
for (auto v : e[u]) {
if (v == fa)
continue;
dfs(v, u); // 先计算好儿子的答案
dp[u] = (dp[u] + dp[u] * g[v] % mod + dp[v] * g[u] % mod) % mod;
for (int i = 0; i < 30; i ++) {
dp[u] = (dp[u] + (1ll << i) * f[u][i][0] % mod * f[v][i][1] % mod + (1ll << i) * f[v][i][0] % mod * f[u][i][1] % mod) % mod;
f[u][i][0] = (f[u][i][0] + f[u][i][0] * g[v] % mod + f[v][i][0] * g[u] % mod) % mod;
f[u][i][1] = (f[u][i][1] + f[u][i][1] * g[v] % mod + f[v][i][1] * g[u] % mod) % mod;
if (i == 0 || i == 1) {
debug(i, u, f[u][i][0], f[u][i][1]);
}
}
g[u] = (g[u] + g[u] * g[v]) % mod;
}
ans = (ans + dp[u]) % mod;
};
dfs(1, 0);
cout << (ans * 2) % mod << endl;
}

J. 数据时间?

tag:模拟

Solution:注意观察细节,可以减少很多码量。

void solve(){
int n;
string h, m;
cin >> n >> h >> m;
if (m.size() == 1){
m = '0' + m;
}
string x, y, z;
map<string, int> cnt1, cnt2, cnt3;
for (int i = 0; i < n; i++){
cin >> x >> y >> z;
if (y.substr(0, 4) != h) continue;
if (y.substr(5, 2) != m) continue;
string z2 = z.substr(0, 2);
if (z2 == "07" || z2 == "08" || z == "09:00:00" || z2 == "18" || z2 == "19" || z == "20:00:00"){//cnt1
cnt1[x]++;
}else if (z2 == "11" || z2 == "12" || z == "13:00:00"){
cnt2[x]++;
}else if (z2 == "22" || z2 == "23" || z2 == "00" || z == "01:00:00"){
cnt3[x]++;
}
}
cout << cnt1.size() << " " << cnt2.size() << " " << cnt3.size() << "\n";
}

K. 可以分开吗?

tag:dfs

Solution:dfs找出所有连通块,同时将连通块周围的灰块放入set去重。

Competing:将字符串当做整数读了(o(╥﹏╥)o)。

int dir[4][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
void solve(){
int n, m;
cin >> n >> m;
vector a(n + 1, vector<pii>(m + 1));
for (int i = 1; i <= n; i ++){
string s;
cin >> s;
s = '$' + s;
for (int j = 1; j <= m; j ++){
a[i][j].fi = s[j] - '0';
}
}
int ans = n * m;
set<pii> st;
function<void(int, int)> dfs = [&](int x, int y){
for (int i = 0; i < 4; i ++){
int tx = x + dir[i][0], ty = y + dir[i][1];
if (tx < 1 || tx > n || ty < 1 || ty > m || a[tx][ty].se != 0)
continue;
if (a[tx][ty].fi == 0){
st.insert({tx, ty});
continue;
}
a[tx][ty].se = 1;
dfs(tx, ty);
}
};
for (int i = 1; i <= n; i ++){
for (int j = 1; j <= m; j ++){
if (a[i][j].fi == 1 && a[i][j].se == 0){
st.clear();
dfs(i, j);
ans = min(ans, (int)st.size());
}
}
}
cout << ans << endl;
}

M. 那是我们的影子

tag:计数 + 组合数学

Description:给定一个3 * n的表格,里面有一些数字和一些问号,你可以在问号处填任意数字(0-9),问满足任意3 * 3的表格都是数独的方案数?。

Solution:模拟一下发现j mod 3相同的列里面填的数字一定相同(顺序可以不一样)。

  • 一个列集出现的数字大于3个不合法。
  • 一个数字出现在多个列集不合法。
  • 一列出现相同的数字不合法。
  • 对于合法的状态:
    • 当前还剩n个数字可用,第一列集还可以填ne[0] = 3 - st[0].size()个数,......。
    • 那么方案数为Cnne[0]Cnne[0]ne[1]Cnne[0]ne[1]ne[2]
    • 一列有x个问号,代表着x个数的顺序可以任意交换,即Axx
struct Comb{
int n, mod;
Comb(int n, int mod){
this->n = n;
this->mod = mod;
init(n);
};
LL qmi(LL a, LL k, LL mod){
LL res = 1;
while (k){
if (k & 1)
res = res * a % mod;
k >>= 1;
a = a * a % mod;
}
return res;
}
vector<LL> fact, infact, inv;
void init(int n){
fact.resize(n + 1); // 存储阶乘
infact.resize(n + 1); // 存储阶乘的逆元
inv.resize(n + 1); // 存储自然数的逆元
fact[0] = infact[0] = inv[1] = 1;
for (int i = 1; i <= n; i ++)
fact[i] = fact[i - 1] * i % mod;
infact[n] = qmi(fact[n], mod - 2, mod);
for (int i = n - 1; ~i; i --)
infact[i] = infact[i + 1] * (i + 1) % mod;
for (int i = 2; i <= n; i ++)
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
}
LL C(LL n, LL m){
if (m < 0 || n - m < 0)
return 0;
return fact[n] * infact[m] % mod * infact[n - m] % mod;
}
LL A(LL n, LL m){
if (n < m || m < 0)
return 0;
return fact[n] * infact[n - m] % mod;
}
};
void solve(){
int n;
cin >> n;
Comb comb(20, mod);
vector g(4, vector<char>(n + 1));
set<int> st[3];
for (int i = 1; i <= 3; i ++)
for (int j = 1; j <= n; j ++){
cin >> g[i][j];
if (g[i][j] == '?')
continue;
st[j % 3].insert(g[i][j] - '0');
}
if (st[0].size() > 3 || st[1].size() > 3 || st[2].size() > 3){ // 某一列集出现3个以上的数
cout << 0 << endl;
return;
}
for (int i = 1; i <= 9; i ++){
bool flag = false;
for (int j = 0; j < 3; j ++){
if (st[j].find(i) != st[j].end() && flag){ // 不同列集包含同一个数
cout << 0 << endl;
return;
}
if (st[j].find(i) != st[j].end()){
flag = true;
}
}
}
for (int i = 1; i <= n; i ++) {
vector<int> cnt(10);
for (int j = 1; j <= 3; j ++) { // 同一个数在同一列出现两次
if (g[j][i] == '?')
continue;
cnt[g[j][i] - '0'] ++;
if (cnt[g[j][i] - '0'] > 1){
cout << 0 << endl;
return;
}
}
}
vector<int> f(n + 1);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= 3; j ++) { // 每一列有多少个问号
if (g[j][i] == '?') {
f[i] ++;
}
}
vector<int> ne(3); // 每一列还剩多少数可以用
int now_use = 0; // 一共还有多少数可以用
for (int j = 0; j < 3; j ++) {
ne[j] = 3 - st[j].size();
now_use += ne[j];
}
// 每一列能够填的数字是哪些
int ans = comb.C(now_use, ne[0]) * comb.C(now_use - ne[0], ne[1]) % mod;
for (int i = 1; i <= n; i ++) // 一列选择的数确定了,那么有几个问号代表可以交换它们的位置
ans = ans * comb.A(f[i], f[i]) % mod;
cout << ans << endl;
}
posted @   Sakura17  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示