牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ (nowcoder.com)
注:本题解只涉及赛时思路和赛后补题收获,人菜佬勿喷
第一场六题,第二场还是六题呜呜呜,感觉比第一场要难点,线段树题没调出来,狠狠哭住了
赛时六题【ABEFIJ】补题【KDG】
A.Tokitsukaze and Bracelet
A-Tokitsukaze and Bracelet_2024牛客寒假算法基础集训营2 (nowcoder.com)
手环有3种属性:普通攻击百分比加成,体力,精神。每次炼成手环时,会对手环的每个属性都随机赋予强化等级,每个属性的强化等级可能为 +0,+1,+2 。强化等级对应的属性值如下:
- 对于普通攻击百分比加成来说: +0 为 100% , +1 为 150% , +2 为 200% ;
- 对于体力和精神来说: +0 会在 {29,30,31,32} 里随机选择, +1 会在 {34,36,38,40} 里随机选择, +2 固定为 45 。
例如,一个普通攻击百分比加成 100% ,体力 45 ,精神 40 的手环的强化等级为 +3 。其中普通攻击力百分比提供了 +0 ,体力提供了 +2 ,精神提供了 +1 。
分析:
签到题,根据题意直接写即可
代码:
void solve(){
int n;
cin >> n;
int a, b, c;
for(int i = 0; i < n; i++){
int ans = 0;
cin >> a >> b >> c;
if(a == 100);
else if(a == 150)
ans++;
else
ans += 2;
if(b >= 29 && b <= 32);
else if(b >= 34 && b <= 40)
ans++;
else
ans += 2;
if(c >= 29 && c <= 32);
else if(c >= 34 && c <= 40)
ans++;
else
ans += 2;
cout << ans << endl;
}
}
B.Tokitsukaze and Cats
B-Tokitsukaze and Cats_2024牛客寒假算法基础集训营2 (nowcoder.com)
分析:
这个题目也不难,要想把一只猫封起来需要把四周都封起来,猫还有可能跳出边界,所以即使在边界也要设置防护网。只需把猫的坐标的四周的边界用map存一下,每只猫需要的防护网直接用4减去已经存在的边界的数量即可
代码:
void solve(){
int n, m, k;
cin >> n >> m >> k;
map<pair<int, int>, int>mp;
int x, y;
int ans = 0;
while(k--){
cin >> x >> y;
ans += 4;
ans -= mp[{x - 1, y}];
ans -= mp[{x + 1, y}];
ans -= mp[{x, y - 1}];
ans -= mp[{x, y + 1}];
mp[{x, y}]++;
}
cout << ans;
}
E.Tokitsukaze and Eliminate (easy)
E-Tokitsukaze and Eliminate (easy)_2024牛客寒假算法基础集训营2 (nowcoder.com)
分析:
这个题直接贪心即可,先记录每个颜色出现的数量,然后从后往前遍历直到出现的颜色等于总数,然后消除即可
这个思路同样适用于hard版本的F题。
代码:
void solve(){
int n;
cin >> n;
vector<ll>a(n + 1);
map<ll, ll>m;
map<ll, ll>mp;
rep(i, 1, n + 1){
cin >> a[i];
m[a[i]]++;
}
ll pos = 0, ans = 0, cnt = n, p = n;
for(int i = n; i >= 1; i--){
if(mp[a[i]] == 0){
mp[a[i]]++;
pos += m[a[i]];
}
if(pos == cnt){
pos = 0;
mp.clear();
ans++;
for(int j = i; j <= p; j++)
m[a[j]]--;
p = i - 1;
cnt = i - 1;
}
}
cout << ans << endl;
}
F.Tokitsukaze and Eliminate (hard)
F-Tokitsukaze and Eliminate (hard)_2024牛客寒假算法基础集训营2 (nowcoder.com)
分析:
同e题
代码:
同e题
I.Tokitsukaze and Short Path (plus)
I-Tokitsukaze and Short Path (plus)_2024牛客寒假算法基础集训营2 (nowcoder.com)
分析:
赛时读题:完全图,最短路,好!跑dij!已经把Dijkstra板子写上去后仔细一看数据范围和题目,发现这就是找规律的题,不需要跑dij。
从数据范围看,直接暴力加权值是会超时的,所以需要寻找更优解。
plus版本是取两个点之间权值较大的那个权值的两倍,可以发现权值越小的,出现次数越少,摸一下样例可以发现从小到大的权值分别出现了0,2,4,6,8...(n - 1)* 2次。所以只需将所有权值从小到大排序,for循环遍历一遍,答案加上对应的权值乘上对应的出现次数即可
代码:
void solve(){
ll n;
cin >> n;
vector<ll>w(n + 1);
for(int i = 1; i <= n; i++){
cin >> w[i];
}
sort(w.begin() + 1, w.end());
ll ans = 0;
for(int i = 1; i <= n; i++){
ans += (i - 1) * 2 * w[i];
}
cout << ans * 2 << endl;
}
J.Tokitsukaze and Short Path (minus)
J-Tokitsukaze and Short Path (minus)_2024牛客寒假算法基础集训营2 (nowcoder.com)
分析:
minus版本与plus版本的区别就是这个题的任意两个点之间的边权成了最小值的两倍。
如果与i题一样每次都取最小值的话是无法通过的,因为会有情况的最短路不是直接连接。
不难发现,最短路无非就只有两种情况:1、两个点直接到达。2、通过最小权值的点进行转接(可以证明没有任何转接方式的权值小于与最小权值转接)
所以只需要统计两点直接距离小于最小权值的四倍的权值即可。(四倍是因为到达最小权值的点需要两倍的最小权值,再从最小权值点到目标点又需要两倍的最小权值)
代码:
void solve(){
ll n;
cin >> n;
vector<ll>w(n), cnt;
for(int i = 0; i < n; i++){
cin >> w[i];
}
sort(w.begin(), w.end());
for(int i = 1; i < n; i++){
if(w[i] < 2 * w[0])
cnt.push_back(w[i]);
}
ll ans = 0;
ans += w[0] * (n - 1) * 2 * 2;//最小权值的点所连接的点
ll sum = 0, flag = 0, h = 0;
for(int i = 0; i < cnt.size(); i++){
ans += 2 * cnt[i] * (n - 2 - i) * 2;//直接连接小于四倍最小权值的情况
sum += (n - 2 - i) * 2;
}
if((n - 2) * (n - 1) - sum > 0)
ans += ((n - 2) * (n - 1) - sum) * w[0] * 4;//需要通过最小权值的转接的情况
cout << ans << endl;
}
K.Tokitsukaze and Password (easy)
K-Tokitsukaze and Password (easy)_2024牛客寒假算法基础集训营2 (nowcoder.com)
分析:
数据范围很小可以直接暴搜,dfs或者循环枚举都可以
代码:
string x, y;
int n, m;
int ans;
map<char, int>mp;
map<string, int>t;
void dfs(string s, int m, map<char, int> mp) {
if(m == n){
int a = stoi(s);
int b = stoi(y);
if(s[0] >= '1' and a <= b and (a % 8 == 0)){
ans++;
}
else if(n == 1){
if (a <= b and (a % 8 == 0)) {
ans++;
}
}
}
else{
if(s[m] == '_'){
string ss = s;
for(char a = '0'; a <= '9'; a++){
ss[m] = a;
dfs(ss, m + 1, mp);
}
}
else if(s[m] <= 'z' and s[m] >= 'a'){
for (char a = '0'; a <= '9'; a++){
string ss = s;
if (mp[a] == true)
continue;
else {
for (int i = 0; i < n; i++){
if (ss[i] == s[m]) ss[i] = a;
}
mp[a] = true;
dfs(ss, m + 1, mp);
mp[a] = false;
}
}
}
else
dfs(s, m + 1, mp);
}
}
void solve(){
ans = 0;
cin >> n;
cin >> x >> y;
dfs(x, 0, mp);
ans %= mod;
cout << ans << endl;
}
D.Tokitsukaze and Slash Draw
D-Tokitsukaze and Slash Draw_2024牛客寒假算法基础集训营2 (nowcoder.com)
分析
这个题赛时的想法是dp,可无奈是个dp废物,不会写dp
赛后发现这问题看成一张图的话,其实就是求k%n之后到0的最短路
所以我们只需要建图跑一下Dijkstra
这个题是求k%n到0的最短路,所以把Dij板子稍微一改即可
代码:
基于链式前向星的Dijkstra
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 5e3 + 10;
ll n, m, s, k;
ll u[N], v[N];
ll cnt, head[N];
ll dist[N], vis[N];
struct Edge{
ll to, dis, next;
}edge[5000050];
void Add_edge(int from, int to, int w){
edge[cnt].to = to;
edge[cnt].dis = w;
edge[cnt].next = head[from];
head[from] = cnt++;
}
void init() {//初始化链式前向星
for (ll i = 0; i < N; ++i) {
edge[i].next = -1;
head[i] = -1;
dist[i] = INF;
vis[i] = 0;
}
cnt = 0;
}
void Dijkstra(ll s){
priority_queue<pair<ll, ll>, vector<pair<ll, ll>>, greater<pair<ll, ll>>> q;
q.push({0, s});
dist[s] = 0;
while(!q.empty()){
ll now = q.top().second;
q.pop();
if(vis[now]) continue;
vis[now] = 1;
for(ll i = head[now]; ~i; i = edge[i].next)
{
ll j = edge[i].to;
if(dist[now] + edge[i].dis < dist[j]){
dist[j] = dist[now] + edge[i].dis;
q.push({dist[j], j});
}
}
}
}
void solve(){
cin >> n >> m >> k;
init();
k %= n;
for(ll i = 1; i <= m; i++)
cin >> u[i] >> v[i];
for(ll i = 0; i < n; i++)
for(ll j = 1; j <= m; j++)
Add_edge(i, (i + u[j]) % n, v[j]);
Dijkstra(k);
if(dist[0] == INF)
cout << -1 << endl;
else
cout << dist[0] << endl;
}
G.Tokitsukaze and Power Battle (easy)
G-Tokitsukaze and Power Battle (easy)_2024牛客寒假算法基础集训营2 (nowcoder.com)
分析:
读题可以发现单点修改,区间查询,很浓的线段树味道
的范围可以看出: 必定是大于0的整数,也就意味着在i到j的区间中选取的两段必定是 和[i,j−1]和[j] 两段才满足题意两端的差值最大。所以我们只需用线段树维护区间和,区间最大值即可
代码:
const ll INF = 1e16 + 10;
const int N = 5e5 + 10;
ll n, m;
ll a[N];
struct segtree{
ll sum;
ll ans;
ll le;
}tr[N << 2];
void push_up(segtree &u, segtree &l, segtree &r){
u.sum = l.sum + r.sum;
u.le = max(l.le, l.sum + r.le);
u.ans = max({l.ans, l.sum + r.le, r.ans});
}
void build(ll u, ll l, ll r){
if(l == r){
tr[u] = {a[l], -INF, -a[l]};
return;
}
ll mid = (l + r) >> 1, tl = u << 1, rs = u << 1 | 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
push_up(tr[u], tr[tl], tr[rs]);
}
void modify(ll l, ll r, ll i, ll x, ll d){
if(l == r){
tr[i].ans = -1e18;
tr[i].sum = d;
tr[i].le = -d;
return;
}
int mid = l + r >> 1, tl = i << 1, rs = i << 1 | 1;
if(x <= mid)
modify(l, mid, tl, x, d);
else
modify(mid + 1, r, rs, x, d);
push_up(tr[i], tr[tl], tr[rs]);
}
segtree query(ll l, ll r, ll i, ll L, ll R){
if(L <= l && r <= R)
return tr[i];
ll mid = l + r >> 1, tl = i << 1, rs = i << 1 | 1;
if(mid >= R)
return query(l, mid, tl, L, R);
else if(mid < L)
return query(mid + 1, r, rs, L, R);
else {
segtree ltre = query(l, mid, tl, L, R), rtre = query(mid + 1, r, rs, L, R), nowtre;
push_up(nowtre, ltre, rtre);
return nowtre;
}
}
void solve(){
cin >> n >> m;
for(int i = 1; i <= n; i++)
cin >> a[i];
build(1, 1, n);
while(m--){
int op, l, r;
cin >> op >> l >> r;
if(op == 1){
modify(1, n, 1, l, r);
}
else
cout << query(1, n, 1, l, r).ans << endl;
}
}
调了好久呜呜呜,modify函数一直出问题爆内存,差点调崩溃掉(...