河南萌新联赛2024第(三)场题解
先给出比赛链接:
https://ac.nowcoder.com/acm/contest/87865
B 正则表达式
按题意取出四个数判断是否满足
Show Code B
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n , ans = 0;
cin >> n;
for (int k = 1; k <= n; ++ k) {
bool flag = 1;
string s , str = "";
cin >> s;
int len = s.size() , cur = 0;
vector< int > a(4);
for (int i = 0; i < len; ++ i) {
if (s[i] != '.') {
str += s[i];
}
if (s[i] == '.' || i == len - 1) {
a[cur] = stoi(str);
str = "";
cur ++;
}
}
for (int i = 0; i < 4; ++ i) {
if (a[i] > 255 || a[i] < 0) flag = 0;
}
if (flag) ans ++;
}
cout << ans << "\n";
}
C Circle
题目问n个圆能把平面分割成几个区域
在已n个圆分割出f(n)个区域的情况下,再画一个圆要使增加的区域最多。
则最优方案一定是新圆穿过这n个圆每个两次
注意n=0时,也算有一个区域,特判即可
Show Code C
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
auto f = [&](ll n) {
if (n) {
return n * n - n + 2;
} else {
return 1ll;
}
};
int tt = 1;
cin >> tt;
while (tt--) {
ll n;
cin >> n;
cout << f(n) << " ";
}
}
E 区间
线段树求区间最大子段和板子题
只需将原数组状态为白色时赋值为1,黑色赋值为1e9,根据数据范围连续的白色对应的子段和一定不会超过1e9,即一个黑色就能截断白色,这样算区间内连续的白色,就能转化为算区间内连续的子段和了
线段树求区间最大子段和板子在此不过多赘述
Show Code E
struct info {
int l , r;
ll sum , lmss , rmss , mss;
};
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
auto ls = [&](int x) { return x << 1; };
auto rs = [&](int x) { return x << 1 | 1; };
int n , q , op , l , r , x;
cin >> n >> q;
vector< ll > a(n + 2 , 1);
vector< info > tr(4 * n + 4);
auto re = [&](int x) {
if (a[x] == 1) {
a[x] = -1e9;
} else {
a[x] = 1;
}
};
auto pushup = [&](info & t , info l , info r) {
t.sum = l.sum + r.sum;
t.lmss = max(l.lmss , l.sum + r.lmss);
t.rmss = max(r.rmss , r.sum + l.rmss);
t.mss = max(max(l.mss , r.mss) , l.rmss + r.lmss);
};
auto build = [&](auto self , int l , int r , int p) -> void {
tr[p] = {l , r , a[l] , a[l] , a[l] , a[l]};
if (l == r) return ;
int mid = (l + r) >> 1;
self(self , l , mid , ls(p));
self(self , mid + 1 , r , rs(p));
pushup(tr[p] , tr[ls(p)] , tr[rs(p)]);
};
auto change = [&](auto self , int u , int x , int w) -> void {
if (tr[u].l == tr[u].r) {
tr[u] = {x , x , w , w , w , w};
return ;
}
int mid = (tr[u].l + tr[u].r) >> 1;
if (x <= mid) {
self(self , ls(u) , x , w);
} else {
self(self , rs(u) , x , w);
}
pushup(tr[u] , tr[ls(u)] , tr[rs(u)]);
};
auto query = [&](auto self , int u , int l , int r) {
if (l <= tr[u].l && tr[u].r <= r) return tr[u];
int mid = (tr[u].l + tr[u].r) >> 1;
if (r <= mid) return self(self , ls(u) , l , r);
if (l > mid) return self(self , rs(u) , l , r);
info T;
pushup(T , self(self , ls(u) , l , mid) , self(self , rs(u) , mid + 1 , r));
return T;
};
build(build , 1, n , 1);
while (q--) {
cin >> op;
if (op == 1) {
cin >> x;
re(x);
change(change , 1 , x , a[x]);
} else {
cin >> l >> r;
info ansinfo = query(query , 1 , l , r);
ll ans = ansinfo.mss;
ans = max(ans , 0ll);
cout << ans << "\n";
}
}
}
F 累加器
存在一个累加器,每次可以对一个数进行加 1 操作,此时在二进制下有一些位发生改变,对一个数 x 进行 y 次累加操作,总共有多少位发生改变。
我们只需算出0y变化的次数再减去0x变化的次数再相减就行
可以发现第n位的数01变化的循环是
设第n位的变化次数为f(n)
当f(n)为奇数时,f(n+1)则为(f(n)-1) / 2
当f(n)为偶数时,f(n+1)则为f(n) / 2
归纳可得f(n+1)为f(n)整除2
而f(1)可以知道就是本身
那么我们就能O(log(n))算出0y变化的次数和0x变化的次数
这个复杂度是可以容忍的
Show Code F
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
auto bitprefix = [&](ll n) { // 拆位前缀和
vector< ll > res(64);
res[0] = n;
for (int i = 1; i <= 61; ++ i) {
res[i] = res[i - 1] / 2ll;
}
return res;
};
auto query = [&](ll l , ll r) {
ll res = 0;
vector< ll > sl = bitprefix(l);
vector< ll > sr = bitprefix(r);
for (int i = 0; i <= 61; ++ i) {
res += (sr[i] - sl[i]);
}
return res;
};
int tt = 1;
cin >> tt;
while (tt--) {
int x , y;
cin >> x >> y;
ll ans = query(x , x + y);
cout << ans << "\n";
}
}
G 求值
由于n最大是1e6
那么我们就能遍历y或者z
但是遍历y再遍历z的复杂度达到了
这是不可以容忍的
有没有什么办法可以优化呢?
可以想到二分优化掉一重循环,
但是能否二分呢?
我们可以拆掉绝对值,那么问题就转化成了
可以判断f(z)具有单调性,即,可二分
那么只需要遍历z,再二分y(
使y+1就是最小的大于w的数(可能不存在)
判断一下这两个数哪个数与w最接近即可
Show Code G
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
auto binsearch = [&](ll y, ll maxn, ll m) {
ll res = 0;
ll l = 0, r = maxn;
while (l <= r) {
ll mid = (l + r) >> 1;
if (mid * y > m) {
r = mid - 1;
} else {
l = mid + 1;
}
}
return r;
};
int tt = 1;
cin >> tt;
while (tt--) {
ll A, B, C, n, w ,ans = 1e18;
ll a, b, c;
cin >> A >> B >> C >> n >> w;
a = min(min(A , B) , C);
c = max(max(A , B) , C);
b = A + B + C - A - C;
ll ba = b - a;
ll ca = c - a;
ll abc = n * a;
if (abc >= w) {
ans = abc - w;
} else { // w > abc
for (ll z = 0; z <= n; ++ z) {
ll cur = abc + z * ca;
ans = min(ans , abs(cur - w));
ll y = binsearch(ba , n - z , w - cur);
cur += y * ba;
ans = min(ans , abs(cur - w));
if (y < n - z) {
cur += ba;
ans = min(ans , abs(cur - w));
}
}
}
cout << ans << "\n";
}
}
H 魔法
求从(1,1)走到(n,m)所需用的最小魔法次数,(hp需>h)
魔法次数显然具有单调性,可二分使用的最小魔法次数
只需判断当前二分到的魔法次数能否使得到(n,m)点的血量大于h
由于只能往下或者往右走,即当前点的状态不会被后续点影响,完全由之前的点决定,即具有无后效性,满足DP的条件
我们可以设dp[i][j][k]为在(i,j)点时使用k次魔法的的血量
dp到(n,m)点判断一下是否大于h就可写出check
状态转移方程可分为使用魔法和不使用魔法,各自还可以再分为从上面下来和从左边过来
x 表示当前二分到的魔法使用次数
Show Code H
constexpr int inf = 0x3f3f3f3f;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n, m, h;
cin >> n >> m >> h;
vector< vector< int >> a(n + 1, vector< int >(m + 1));
vector< vector< int >> dp(n + 1, vector< int >(m + 1));
vector< vector< int >> pre(n + 1, vector< int >(m + 1));
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
cin >> a[i][j];
}
}
auto check = [&](int x) {
int res = 0;
vector< vector< vector< int >>> dp(n + 1, vector< vector< int >>(m + 1 , vector< int >(n * m + 16 , inf)));
dp[1][1][0] = 0;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
for (int k = 0; k <= x; ++ k) {
dp[i][j][k] = min(dp[i][j][k], dp[i - 1][j][k] + a[i][j]);
dp[i][j][k] = min(dp[i][j][k], dp[i][j - 1][k] + a[i][j]);
if (k) {
dp[i][j][k] = min(dp[i][j][k], dp[i - 1][j][k - 1]);
dp[i][j][k] = min(dp[i][j][k], dp[i][j - 1][k - 1]);
}
}
}
}
for (int k = 0; k <= x; ++ k) {
if (dp[n][m][k] < h) {
res = 1;
}
}
return res;
};
int l = 0, r = n * m;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) {
r = mid - 1;
} else {
l = mid + 1;
}
}
cout << l << "\n";
}
I 游戏
最短路板子题
只需把图按状态分为两个,不妨设g[1]表示拿到钥匙后能走的(由于拿到钥匙后,原本不需要钥匙的边也能走,所以g[1]包括了原本不需要钥匙的边),g[0]表示没有钥匙才能走的图,只需分别在g[0]跑一下dij判断能否从1点到达终点;在g[0]跑dij能否从1点跑到k点,在g[1]跑一下能否从k点到达终点
不能则输出-1,能则输出最小值即可
Show Code I
constexpr ll inf = 0x3f3f3f3f3f3f3f3f;
class edge {
public:
ll v, w;
};
struct node {
ll dis, u;
bool operator>(const node& a) const { return dis > a.dis; }
};
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n, m, k;
cin >> n >> m >> k;
vector< vector< vector< edge >>> g(2, vector< vector< edge >>(n + 1, vector< edge >(0) ) );
auto dij = [&](int begin, int end, int op) {
vector< ll > dis(n + 1 , inf);
vector< ll > vis(n + 1);
priority_queue< node, vector< node >, greater< node > > q;
dis[begin] = 0;
q.push({0, begin});
while (!q.empty()) {
int u = q.top().u;
q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (auto ed : g[op][u]) {
int v = ed.v, w = ed.w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
q.push({dis[v], v});
}
}
}
return dis[end];
};
for (int i = 1; i <= m; ++ i) {
ll a, b, c, op;
cin >> a >> b >> c >> op;
if (op == 1) {
g[0][a].push_back({b, c});
g[0][b].push_back({a, c});
}
g[1][a].push_back({b, c});
g[1][b].push_back({a, c});
}
ll ans1 = dij(1, n, 0);
ll ans2 = dij(1, k, 0) + dij(k, n, 1);
if (ans1 >= inf && ans2 >= inf) {
cout << -1 << "\n";
} else {
cout << min(ans1, ans2) << "\n";
}
}
J keillempkill学姐の卷积
按题意模拟即可
Show Code C
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n , m , len;
cin >> n >> m;
len = m - n + 1;
vector< vector< ll >> ck(n + 1 , vector< ll >(n + 1)); // Convolution kernel
vector< vector< ll >> ma(m + 1 , vector< ll >(m + 1)); // matrix
vector< vector< ll >> ansl(len , vector< ll >(len)); // matrix
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
cin >> ck[i][j];
}
}
for (int i = 1; i <= m; ++ i) {
for (int j = 1; j <= m; ++ j) {
cin >> ma[i][j];
}
}
auto mul = [&] (int x , int y) {
ll res = 0;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
res += ck[i][j] * ma[i + x][j + y];
}
}
return res;
};
for (int i = 0; i < len; ++ i) {
for (int j = 0; j < len; ++ j) {
ansl[i][j] = mul(i , j);
}
}
for (int i = 0; i < len; ++ i) {
for (int j = 0; j < len; ++ j) {
cout << ansl[i][j] << " \n"[j == len - 1];
}
}
}
K 暴食之史莱姆
一个数往左边最多能吃掉的个数就是吃掉左边最近的一个比它小的数所吃的个数,往右边同理
要是中间有比它大的
就吃掉那个最近的一个比它小的数从而变成那个数
由于可以随意左右移动,所以当前这个数所能吃的最大数量就是吃左边与吃右边相加
可以用单调栈维护左边最近的一个比当前小的数
Show Code K
constexpr int inf = 0x3f3f3f3f;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n;
cin >> n;
vector< int > a(n + 2 , inf);
vector< int > eat1(n + 2);
vector< int > eat2(n + 2);
vector< int > eat(n + 2);
map< int, int > mp1;
map< int, int > mp2;
vector< int > sta1(n + 2);
vector< int > sta2(n + 2);
sta1[0] = -1;
sta2[0] = -1;
int top1 = 1, top2 = 1;
//在单调sta中插入x同时retun插入前的栈顶元素
auto f = [&](int op, int x) {
ll res;
if (op == 1) {
while (top1 >= 1 && sta1[top1] > x) {
top1--;
}
res = sta1[top1];
top1++;
sta1[top1] = x;
} else {
while (top2 >= 1 && sta2[top2] > x) {
top2--;
}
res = sta2[top2];
top2++;
sta2[top2] = x;
}
return res;
};
for (int i = 1; i <= n; ++ i) cin >> a[i];
mp1[a[1]] = 1;
sta1[1] = a[1];
for (int i = 2; i <= n; ++ i) { // left
int pre = f(1, a[i]);
if (pre >= 0) {
eat1[i] = eat1[mp1[pre]] + 1;
}
mp1[a[i]] = i;
}
mp2[a[n]] = n;
sta2[1] = a[n];
for (int i = n - 1; i >= 1; -- i) { // right
int pre = f(2, a[i]);
if (pre >= 0) {
eat2[i] = eat2[mp2[pre]] + 1;
}
mp2[a[i]] = i;
}
for (int i = 1; i <= n; ++ i) { // all
eat[i] = eat1[i] + eat2[i];
cout << eat[i] << " \n"[i == n];
}
}
L SSH
按题意模拟,可以考虑用map建立字符串与set数组的映射,再对set用count判断是否存在某个字符,可以使代码简洁很多
Show Code L
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int m , n , q;
cin >> m >> n >> q;
map< string, string > mp1; // pri , pub
map< string, set< string >> mp2; // user , ip
map< string, set< string >> mp3; // pub , user
for (int i = 1; i <= m; ++ i) {
string pub , pri;
cin >> pub >> pri;
mp1[pri] = pub;
}
for (int i = 1; i <= n; ++ i) {
string ip;
int k;
cin >> ip >> k;
for (int j = 1; j <= k; ++ j) {
string user;
int t;
cin >> user >> t;
mp2[user].insert(ip);
for (int i3 = 1; i3 <= t; ++ i3) {
string pub;
cin >> pub;
mp3[pub].insert(user);
}
}
}
while (q--) {
string user , ip , pri;
cin >> user >> ip >> pri;
string pub = mp1[pri];
if (mp3[pub].count(user) == 1 && mp2[user].count(ip) == 1) {
cout << "Yes\n";
} else {
cout << "No\n";
}
}
}
(PS:菜菜,目前只写了10题的题解)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)