2024牛客寒假算法基础集训营2
A
模拟
map<int, int> mp;
void solve()
{
mp[100] = 0;
mp[150] = 1;
mp[200] = 2;
mp[29] = 0;
mp[30] = 0;
mp[31] = 0;
mp[32] = 0;
mp[34] = 1;
mp[36] = 1;
mp[38] = 1;
mp[40] = 1;
mp[45] = 2;
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
int ans=0;
for (int j = 0; j < 3; j++)
{
int x;
cin >> x;
ans += mp[x];
} cout << ans << endl;
}
}
B
计数
int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, 1, -1};
void solve()
{
int n, m, k;
cin >> n >> m >> k;
vector<vector<int>> dist(n + 10, vector<int>(m + 10, 0));
queue<pii> q;
int ans = 4 * k, cnt = 0;
for (int i = 1; i <= k; i++)
{
int x, y;
cin>>x>>y;
dist[x][y] = 1;
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (dist[i][j])
{
for (int k = 0; k < 4; k++)
{
int xx = i + dx[k], yy = j + dy[k];
if (dist[xx][yy])
cnt++;
}
}
cout << ans - cnt / 2 << endl;
}
C
发现答案只跟max,min,len有关
如果max,min确定了,那么答案就是\(2^{i-j-1}\)
考虑枚举max,用字典树来查询满足条件的右端点,并查询\(\frac{1}{2^i}\)的和
struct Trie
{
int root, tot, nex[N * M][2];
ll val[N * M]; // N 输入的字符串的长度和 ,M字符集
int newnode()
{
memset(nex[tot], 0, sizeof nex[tot]);
val[tot] = 0;
return tot++;
}
void init()
{
memset(nex[0], 0, sizeof nex[0]);
val[0] = 0;
tot = 1;
root = newnode();
}
void upd(ll x, ll v)
{
int id = root;
for (int i = 30; ~i; i--)
{
int t = (x >> i) & 1;
if (!nex[id][t])
nex[id][t] = newnode();
id = nex[id][t];
val[id] = (val[id] + v) % mod;
}
}
ll ask(ll x, ll k)
{
int id = root;
ll res = 0;
for (int i = 30; ~i; i--)
{
int t = (x >> i) & 1;
if (((k >> i) & 1) == 1)
{
if (nex[id][t])
res = (res + val[nex[id][t]]) % mod;
id = nex[id][t ^ 1];
}
else
id = nex[id][t];
}
res = (res + val[id]) % mod;
return res;
}
} tr;
void solve()
{
auto qmi = [&](ll a, ll b) -> ll
{
ll res = 1;
while (b)
{
if (b & 1)
res = res * a % mod;
b >>= 1;
a = a * a % mod;
}
return res;
};
int n, k;
cin >> n >> k;
vector<ll> a(n + 1);
for (int i = 1; i <= n; i++)
cin >> a[i];
sort(a.begin() + 1, a.end());
tr.init();
ll ans = 0;
for (int i = 1; i <= n; i++)
{
ll res = (1 + tr.ask(a[i], k) * qmi(2, i - 1)) % mod;
tr.upd(a[i], qmi(qmi(2, i), mod - 2));
ans = (ans + res) % mod;
}
cout << ans << endl;
}
D
1.dp
预处理出走i步的最小代价\(cost[i]\),然后dp
void solve()
{
int n, m, k;
cin >> n >> m >> k;
vector<ll> f(n + 1, 1e18);
f[n - k] = 0;
vector<ll> cost(n + 1, 1e18);
for (int i = 1; i <= m; i++)
{
ll v, w;
cin >> v >> w;
for (int i = 1; i <= n; i++)
cost[i * v % n] = min(cost[i * v % n], i * w);
}
for (int i = 1; i < n; i++)
for (int j = 0; j < n; j++)
{
int to = (j - i + n) % n;
f[to] = min(f[to], f[j] + cost[i]);
}
if (f[0] == 1e18)
f[0] = -1;
cout << f[0] << endl;
}
2.二进制优化多重背包
一种卡片最多使用n次,所以可以把问题看作一个多重背包问题,用二进制优化即可
void solve()
{
int n, m, k;
cin >> n >> m >> k;
vector<vector<pll>> e(n + 1);
vector<ll> f(n + 1, 1e18);
f[k - 1] = 0;
for (int i = 1; i <= m; i++)
{
ll a, b, num = n;
cin >> a >> b;
for (ll j = 1; j <= num; j *= 2)
{
num -= j;
for (int k = 0; k < n; k++)
f[(k + a * j) % n] = min(f[(k + a * j) % n], f[k] + j * b);
}
if (num)
for (int k = 0; k < n; k++)
f[(k + a * num) % n] = min(f[(k + a * num) % n], f[k] + num * b);
}
if (f[n - 1] == 1e18)
{
cout << "-1" << endl;
return;
}
cout << f[n - 1] << endl;
}
3.最短路
在%n意义下,从k走到0的最短路
建边跑朴素diijkstra即可(优化反而慢)
void solve()
{
int n, m, k;
cin >> n >> m >> k;
vector<vector<pll>> e(n + 1);
vector<ll> dist(n + 1, 1e18);
for (int i = 1; i <= m; i++)
{
ll a, b;
cin >> a >> b;
for (int j = 0; j < n; j++)
e[j].push_back({(j + a) % n, b});
}
auto dij = [&](int s, int t) -> ll
{
vector<ll> dist(n + 1, 1e18);
vector<bool> vis(n + 1, 0);
dist[s] = 0;
while (true)
{
int x = -1;
for (int i = 0; i < n; i++)
if (!vis[i] && dist[i] != 1e18)
if (x == -1 || dist[i] < dist[x])
x = i;
if (x == t || x == -1)
break;
vis[x] = 1;
for (auto [to, w] : e[x])
dist[to] = min(dist[to], dist[x] + w);
}
if (dist[t] != 1e18)
return dist[t];
return -1;
};
cout << dij(k - 1, n - 1) << endl;
}
E
每次选颜色最右边的位置最小的
F
线段树维护每种颜色最右边的位置,每次操作选位置最左的
这样可以把每次操作优化为单log
struct node
{
ll pos, col;
};
struct segtree
{
int n;
vector<node> a;
segtree(int _n) : n(_n * 4 + 10), a(n + 1) {}
void update(int id)
{
a[id].pos = min(a[id * 2].pos, a[id * 2 + 1].pos);
if (a[id * 2].pos < a[id * 2 + 1].pos)
a[id].col = a[id * 2].col;
else
a[id].col = a[id * 2 + 1].col;
}
void build(int id, int l, int r, vector<int> &arr)
{
if (l == r)
a[id].pos = arr[l], a[id].col = l;
else
{
int mid = l + r >> 1;
build(id * 2, l, mid, arr);
build(id * 2 + 1, mid + 1, r, arr);
update(id);
}
}
void change(int id, int l, int r, int pos, int t)
{
if (l == r) // 叶子节点
a[id].pos = t;
else
{
int mid = l + r >> 1;
if (pos <= mid)
change(id * 2, l, mid, pos, t);
else
change(id * 2 + 1, mid + 1, r, pos, t);
update(id);
}
}
pii query(int id, int l, int r, int ql, int qr)
{
if (l == ql && r == qr)
return {a[id].pos, a[id].col};
int mid = l + r >> 1;
if (qr <= mid)
return query(id * 2, l, mid, ql, qr);
else if (ql > mid)
return query(id * 2 + 1, mid + 1, r, ql, qr);
else
{
auto t1 = query(id * 2, l, mid, ql, mid);
auto t2 = query(id * 2 + 1, mid + 1, r, mid + 1, qr);
if (t1.x < t2.x)
return t1;
return t2;
}
}
};
void solve()
{
int n, opt;
cin >> n;
vector<int> a(n + 1);
vector<vector<int>> e(n + 1);
vector<int> dist(n + 1, 1e9);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
e[a[i]].push_back(i);
}
for (int i = 1; i <= n; i++)
if (e[i].size())
dist[i] = e[i].back();
segtree seg(n);
seg.build(1, 1, n, dist);
int r = n, ans = 0;
int cnt = 1;
while (r > 0)
{
ans++;
auto [pos, col] = seg.query(1, 1, n, 1, n);
for (int i = pos; i <= r; i++)
{
int u = a[i];
e[u].pop_back();
int res = (e[u].size() ? e[u].back() : 1e9);
seg.change(1, 1, n, u, res);
}
r = pos - 1;
}
cout << ans << endl;
}
G
设选择的答案区间为\([i,j]\)
有一个贪心结论,对手一定只能选最右边的一个,左端点一定是l,证明显然
s表示a的前缀和
那么答案就是\(s[j-1]-a[j]-s[l-1]\)
等效为\(s[j]-s[l-1]-2*a[j]\)
那么怎样才能一个j,满足上式值最大?
考虑用线段树维护\(s[j]-2*a[j]\)
然后原来的操作1,单点修改就变成了在现在的线段树上进行一个单点修改+区间修改
在额外用树状数组维护一下答案里的\(s[l-1]\)这是一个单点修改
struct node
{
ll t, val, sz, mx;
};
struct segtree
{
int n;
vector<node> a;
segtree(int _n) : n(_n * 4 + 10), a(n + 1) {}
void update(int id)
{
a[id].val = a[id * 2].val + a[id * 2 + 1].val;
a[id].mx = max(a[id * 2].mx, a[id * 2 + 1].mx);
}
void settag(int id, ll t)
{
a[id].val = a[id].val + t * (a[id].sz);
a[id].t = a[id].t + t;
a[id].mx = a[id].mx + t;
}
void pushdown(int id)
{
if (a[id].t)
{
settag(id * 2, a[id].t);
settag(id * 2 + 1, a[id].t);
a[id].t = 0;
}
}
void build(int id, int l, int r, vector<ll> &arr)
{
a[id].t = 0;
a[id].sz = r - l + 1;
if (l == r)
a[id].val = a[id].mx = arr[l];
else
{
int mid = l + r >> 1;
build(id * 2, l, mid, arr);
build(id * 2 + 1, mid + 1, r, arr);
update(id);
}
}
void modify(int id, int l, int r, int ql, int qr, ll t)
{
if (l == ql && r == qr)
{
settag(id, t);
return;
}
int mid = l + r >> 1;
pushdown(id);
if (qr <= mid)
modify(id * 2, l, mid, ql, qr, t);
else if (ql > mid)
modify(id * 2 + 1, mid + 1, r, ql, qr, t);
else
{
modify(id * 2, l, mid, ql, mid, t);
modify(id * 2 + 1, mid + 1, r, mid + 1, qr, t);
}
update(id);
}
ll query(int id, int l, int r, int ql, int qr)
{
if (l == ql && r == qr)
return a[id].mx;
int mid = l + r >> 1;
pushdown(id);
if (qr <= mid)
return query(id * 2, l, mid, ql, qr);
else if (ql > mid)
return query(id * 2 + 1, mid + 1, r, ql, qr);
else
{
return max(query(id * 2, l, mid, ql, mid), query(id * 2 + 1, mid + 1, r, mid + 1, qr));
}
}
};
struct BIT
{
int n;
vector<ll> a;
BIT(int _n) : n(_n + 3), a(n + 1) {}
int lb(int x) { return x & -x; }
void build(int n, vector<ll> &s)
{
for (int i = 1; i <= n; i++)
{
a[i] += s[i];
int fa = i + lb(i);
if (fa <= n)
a[fa] += a[i];
}
}
void add(int x, int y)
{
for (; x < n; x += lb(x))
a[x] += y;
}
ll query(int x)
{
ll res = 0;
for (; x; x ^= lb(x))
res += a[x];
return res;
}
};
void solve()
{
int n, q;
cin >> n >> q;
vector<ll> a(n + 2), s(n + 2, 0);
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
s[i] = s[i - 1] + a[i];
for (int i = 1; i <= n; i++)
s[i] -= 2 * a[i];
BIT bit(n);
bit.build(n, a);
segtree seg(n);
seg.build(1, 1, n, s);
int opt, l, r;
for (int i = 1; i <= q; i++)
{
cin >> opt >> l >> r;
if (opt == 1)
{
seg.modify(1, 1, n, l, l, 2 * a[l] - 2 * r);
seg.modify(1, 1, n, l, n, r - a[l]);
bit.add(l, r - a[l]);
a[l] = r;
}
else
{
ll res = seg.query(1, 1, n, l + 1, r) - bit.query(l - 1);
cout << res << endl;
}
}
}
I
化简题目中的边权计算公式可以发现,本质上是\(2*max(a[u],a[v])\)
贪心的考虑\(dist(i,j)\)一定是直接从\(i\)到\(j\)
将所有点按照点权排序,枚举考虑每个点对答案的贡献
贡献分为两类,比\(a[i]\)小的是\(2*a[i]\),比\(a[i]\)大的是\(2*a[j]\)
二分分界点,大的用前缀和维护即可
void solve()
{
int n;
ll ans = 0, sum = 0;
cin >> n;
vector<ll> a(n + 1), b(n + 1);
vector<ll> s(n + 1);
for (int i = 1; i <= n; i++)
cin >> a[i], b[i] = a[i], sum += a[i];
sort(b.begin(), b.end());
for (int i = 1; i <= n; i++)
s[i] = b[i] + s[i - 1];
for (int i = 1; i <= n; i++)
{
int l = 1, r = n;
while (l < r)
{
int mid = (l + r + 1) >> 1;
if (b[mid] <= a[i])
l = mid;
else
r = mid - 1;
}
ans += 2 * (a[i] * l);
ans += 2 * (s[n] - s[l]);
}
cout << ans - 2 * sum << endl;
}
j
思路类似\(I\)
边权本质是\(2*min(a[i],a[j])\)
有两种情况:
直接从\(i\)到\(j\),
先从\(i\)到边权最小的\(k\),再从\(k\)到\(j\)
然后用类似\(I\)的方法二分算贡献即可
void solve()
{
int n;
ll ans = 0, sum = 0;
multiset<ll> st;
cin >> n;
vector<ll> a(n + 1), b(n + 1), s(n + 1);
for (int i = 1; i <= n; i++)
cin >> a[i], b[i] = a[i];
sort(b.begin() + 1, b.end());
for (int i = 1; i <= n; i++)
s[i] = s[i - 1] + b[i];
ll mn = 1e18;
for (int i = 1; i <= n; i++)
mn = min(mn, a[i]);
for (int i = 1; i <= n; i++)
{
int l = 1, r = n;
while (l < r)
{
int mid = (l + r + 1) >> 1;
if (2 * min(b[i], b[mid]) <= 4 * mn)
l = mid;
else
r = mid - 1;
}
ll res = 2 * s[i] + 2 * b[i] * (n - i);
ans += 2 * s[min(l, i)];
ans += 2 * b[i] * max(0, l - i);
ans += 4 * mn * (n - l);
ans -= min(2 * b[i], 4 * mn);
}
cout << ans << endl;
}
K
暴力枚举所有情况计算即可
void solve()
{
int n, x, ans = 0;
cin >> n;
vector<char> a(n + 1);
vector<int> path(n + 1, 0), vis(100, 0);
vector<int> mp(1000, -1);
for (int i = 1; i <= n; i++)
cin >> a[i];
cin >> x;
auto dfs = [&](auto dfs, int u) -> void
{
if (u > n)
{
if (n > 1 && !path[1])
return;
int p = 0, sum = 0;
for (int i = 1; i <= n; i++)
{
p = (p * 10 + path[i]) % 8;
sum = sum * 10 + path[i];
}
ans += (!p && sum <= x);
return;
}
if (a[u] <= 'z' && a[u] >= 'a')
{
if (mp[a[u]] != -1)
{
path[u] = mp[a[u]];
dfs(dfs, u + 1);
}
else
{
for (int i = 0; i <= 9; i++)
if (!vis[i])
{
vis[i] = 1;
mp[a[u]] = i;
path[u] = i;
dfs(dfs, u + 1);
vis[i] = 0;
mp[a[u]] = -1;
}
}
}
else if (a[u] <= '9' && a[u] >= '0')
{
path[u] = a[u] - '0';
dfs(dfs, u + 1);
}
else
{
for (int i = 0; i <= 9; i++)
{
path[u] = i;
dfs(dfs, u + 1);
}
}
};
dfs(dfs, 1);
cout << ans << endl;
}