牛客练习赛141总结
牛客练习赛141_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ
题意:有 \(n\) 个物品,价值为 \(a_i\),两个人轮流取,求各自的拿到的物品的最大价值和。
题解:排序后模拟取。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define i128 __int128
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 1e5 + 5;
int n, a[N];
void solve()
{
n = read();
for(int i = 1; i <= n; ++i) a[i] = read();
sort(a + 1, a + n + 1);
ll sum1 = 0, sum2 = 0;
for(int i = n; i >= 1; --i)
{
if((i & 1) == (n & 1)) sum1 += a[i];
else sum2 += a[i];
}
printf("%lld %lld\n", sum1, sum2);
}
int main()
{
int T = 1;
while(T--) solve();
return 0;
}
题意:有一个长度为 \(n\) 的序列,每次操作可以使任意位置 \(+1\),问最少的操作次数,使得能找到一个长度为 \(m\) 的奇偶交替的子数组。
题解:前缀和求出一个区间在奇数位上的奇数和偶数,枚举长度为 \(m\) 的子数组求最小值。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define i128 __int128
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 1e5 + 5;
int n, m, a[N], c[N];
void solve()
{
n = read(), m = read();
for(int i = 1; i <= n; ++i) a[i] = read(), c[i] = c[i - 1] + ((i & 1) == (a[i] & 1));
int ans = 0x3f3f3f3f;
for(int i = 1; i <= n - m + 1; ++i)
{
int t = c[i + m - 1] - c[i - 1];
ans = min(ans, min(m - t, t));
}
printf("%d\n", ans);
}
int main()
{
int T = 1;
while(T--) solve();
return 0;
}
题意:给定一个长度为 \(n\) 的字符串 \(s\) 和一个幸运数字 \(x\),定义一个字符串的价值为逆序对个数,对于 \(s\) 的所有子串 \(t\),求 \(t\) 的价值与幸运数字 \(x\) 的差的绝对值的最小值。
题解:设子串区间为 \([l,r]\),容易想到随着 \(l\) 增加,\(r\) 单调不降,枚举 \(l\),用单指针维护 \(r\),当左端点向右移动,减少的逆序对个数为所有大于 \(s_l\) 的字符,若当前逆序对个数小于 \(x\),则不断向右移动右端点,增加的逆序对个数为所有大于 \(s_{r+1}\) 的字符,途中得到的所有答案取 \(min\) 即为答案。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define i128 __int128
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 2e5 + 25;
char s[N];
ll x;
ll cnt[26];
void solve()
{
scanf(" %s", s + 1);
int n = strlen(s + 1);
x = read();
int l = 1, r = 0;
ll sum = 0, ans = x;
while(l <= n)
{
while(sum <= x && r < n)
{
++r;
cnt[s[r] - 'a']++;
for(int i = s[r] - 'a' + 1; i < 26; ++i) sum += cnt[i];
ans = min(ans, abs(x - sum));
}
if(l <= r)
{
--cnt[s[l] - 'a'];
for(int i = 0; i < s[l] - 'a'; ++i) sum -= cnt[i];
++l;
ans = min(ans, abs(x - sum));
}
}
printf("%lld\n", ans);
}
int main()
{
int T = 1;
while(T--) solve();
return 0;
}
题意:给定一棵树,对于每个节点 \(i\),求最大值 \(a_i\oplus a_j\),其中 \(j\) 与 \(i\) 没有祖先关系(但可以有 \(j=i\))。
题解:采用正序DFS+反序DFS,用一个 \(trie\) 树处理最大值,当遍历到节点 \(i\) 时,求最大值 \(a_i\oplus a_j\),当遍历完节点 \(i\) 的所有子树后,向 \(trie\) 树中加入 \(a_i\)。
这样做的话,当遍历到节点 \(i\) 时,\(trie\) 树上有所有 \(dfs\) 序小于 \(i\) 且与 \(i\) 没有祖先关系的节点的值,再反序DFS即为答案。
一点联想:考虑 \(tarjan\) 算法求有向图的强联通分量时,将边分为了树边,返祖边,横叉边三种,处理横叉边的方法是:用一个栈记录根节点到当前节点路径的所有节点,那些已经遍历过但不在栈中的点,就是横叉边指向的点,也即没有祖先关系的点。
不会正反DFS,也可以用可持久化 \(trie\) 树维护子树信息,全局 \(trie\) 树维护整体-根节点到当前节点的信息,在做差的 \(trie\) 树上处理询问,复杂度为 \(O(n\log n)\)。
可持久化trie做法
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define i128 __int128
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 5e5 + 5;
int n, a[N];
vector<int> to[N];
struct Trie1
{
int ch[N * 25][2], Size[N * 25], root, tot;
Trie1()
{
memset(ch, 0, sizeof(ch));
memset(Size, 0, sizeof(Size));
root = 1, tot = 1;
}
void insert(int x, int val)
{
int now = root;
for(int i = 29; i >= 0; --i)
{
int d = (x >> i) & 1;
if(!ch[now][d]) ch[now][d] = ++tot;
now = ch[now][d];
Size[now] += val;
}
}
int get(int x)
{
int ans = 0, now = root;
for(int i = 29; i >= 0; --i)
{
int d = (x >> i) & 1;
if(Size[ch[now][d ^ 1]] > 0) ans += (1 << i), now = ch[now][d ^ 1];
else now = ch[now][d];
}
return ans;
}
}t1;
int ch[N * 200][2], Size[N * 200], tot, root[N];
void insert(int &k, int p, int x)
{
k = ++tot;
int now = tot;
for(int i = 29; i >= 0; --i)
{
ch[now][0] = ch[p][0], ch[now][1] = ch[p][1];
int d = (x >> i) & 1;
ch[now][d] = ++tot;
Size[tot] = Size[ch[p][d]] + 1;
now = ch[now][d], p = ch[p][d];
}
}
int dfn[N], low[N], rk[N], num;
int ans[N];
void dfs1(int k, int fa)
{
dfn[k] = low[k] = ++num, rk[num] = k;
for(auto v : to[k])
{
if(v == fa) continue;
dfs1(v, k);
}
low[k] = num;
}
int query(int l, int r, int x)
{
// printf("x = %d\n", x);
int ans = 0, p = t1.root ;
// printf("l = %d, r = %d\n", l, r);
for(int i = 29; i >= 0; --i)
{
int d = (x >> i) & 1;
// printf("i = %d, p = %d, Size = %d %d %d, d = %d\n", i, p, t1.Size[t1.ch[p][d ^ 1]], Size[ch[r][d ^ 1]], Size[ch[l][d ^ 1]], d);
assert(t1.Size[t1.ch[p][d ^ 1]] - Size[ch[r][d ^ 1]] + Size[ch[l][d ^ 1]] >= 0);
if(t1.Size[t1.ch[p][d ^ 1]] - Size[ch[r][d ^ 1]] + Size[ch[l][d ^ 1]] > 0)
{
ans += (1 << i);
l = ch[l][d ^ 1], r = ch[r][d ^ 1], p = t1.ch[p][d ^ 1];
}else l = ch[l][d], r = ch[r][d], p = t1.ch[p][d];
}
return ans;
}
void dfs2(int k, int fa)
{
ans[k] = query(root[dfn[k] - 1], root[low[k]], a[k]);
t1.insert(a[k], -1);
for(auto v : to[k])
{
if(v == fa) continue;
dfs2(v, k);
}
t1.insert(a[k], 1);
}
void solve()
{
n = read();
for(int i = 1; i <= n; ++i) a[i] = read(), t1.insert(a[i], 1);
for(int i = 1; i < n; ++i)
{
int u = read(), v = read();
to[u].emplace_back(v);
to[v].emplace_back(u);
}
dfs1(1, 0);
for(int i = 1; i <= n; ++i)
{
insert(root[i], root[i - 1], a[rk[i]]);
}
dfs2(1, 0);
for(int i = 1; i <= n; ++i) printf("%d ", ans[i]);
}
int main()
{
int T = 1;
while(T--) solve();
return 0;
}
题意:给定长度为 \(n\) 的序列,求有多少个长度为 \(5\) 的子序列 \(b=\{b_1,b_2,b_3,b_4,b_5\}\) 满足大小关系为 \(b_2<b_1<b_3<b_5<b_4\)。
题解:发现 \(b_2<b_1<b_3\) 与 \(b_3<b_5<b_4\) 类似,以下只考虑求 \(b_2<b_1<b_3\):
枚举 \(a_i=b_3\),记前 \(i-1\) 个数中有 \(c\) 个小于 \(a_i\) 的数,从其中选择两个的方案数为 \(C_{c}^{2}\),其中大小关系为 \(b_1<b_2,b_1=b_2,b_1<b_2\),其中 \(b_1=b_2<b_3\) 容易算出,\(b_1<b_2<b_3\) 为三元上升子序列计数,容斥即可算出 \(b_2<b_1<b_3\) 的方案数。
考虑一种暴力做法:枚举 \(a_i=b_3\),在前 \(i-1\) 个数中,设 \(cnt_i\) 为值为 \(i\) 的数的个数,设 \(num_i\) 为较大值为 \(i\) 的逆序对数,枚举到 \(a_{i+1}=b_3\) 时,将 \(a_i\) 对 \(cnt\) 和 \(num\) 的计算出来,发现可以用分块维护 \(cnt\) 和 \(num\) 数组。
分块做法
#include<bits/stdc++.h>
using namespace std;
#define int ll
#define ll long long
#define ull unsigned long long
#define int128 __int128
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 3e5 + 5;
int n, a[N];
int Size, block;
int belong[N], L[N], R[N];
ll cnt[N], num[N]; // 元素个数,小于它的逆序数
ll c1[N], c2[N]; // 块中元素个数,块中逆序数总和
ll tag[N]; // 整体加标记
void pushdown(int b)
{
if(tag[b])
{
for(int i = L[b]; i <= R[b]; ++i) num[i] += cnt[i] * tag[b];
tag[b] = 0;
}
}
void add(int x)
{
int b = belong[x];
for(int i = b + 1; i <= block; ++i) ++tag[i], c2[i] += c1[i];
pushdown(b);
for(int i = x + 1; i <= R[b]; ++i) num[i] += cnt[i], c2[b] += cnt[i];
++cnt[x], ++c1[b];
}
ll query(int x)
{
int b = belong[x];
ll ans = 0;
for(int i = 1; i < b; ++i) ans += c2[i];
pushdown(b);
for(int i = L[b]; i < x; ++i) ans += num[i];
return ans;
}
ll l[N], r[N];
void print(int128 ans)
{
ll sta[100] = {0}, top = 0;
do
{
sta[++top] = ans % 10;
ans /= 10;
}while(ans);
while(top) printf("%lld", sta[top--]);
printf("\n");
}
void solve()
{
n = read();
for(int i = 1; i <= n; ++i) a[i] = read();
Size = sqrt(n);
for(int i = 1; i <= n; i += Size) belong[i] = 1;
for(int i = 2; i <= n; ++i) belong[i] += belong[i - 1];
for(int i = 1; i <= n; ++i)
{
if(belong[i] != belong[i - 1]) L[belong[i]] = i, R[belong[i - 1]] = i - 1;
}
block = belong[n];
R[belong[n]] = n;
for(int i = 1; i <= n; ++i)
{
add(a[i]);
if(i < n) l[i + 1] = query(a[i + 1]);
}
for(int i = 0; i <= n + 1; ++i) cnt[i] = num[i] = 0;
for(int i = 0; i <= block + 1; ++i) c1[i] = c2[i] = 0, tag[i] = 0;
for(int i = 1; i <= n; ++i) a[i] = n - a[i] + 1;
for(int i = n; i >= 1; --i)
{
add(a[i]);
if(i > 1) r[i - 1] = query(a[i - 1]);
}
int128 ans = 0, p1, p2;
for(int i = 3; i + 2 <= n; ++i)
{
p1 = l[i], p2 = r[i];
ans += p1 * p2;
}
print(ans);
}
signed main()
{
int T = 1;
while(T--) solve();
return 0;
}
F-三角符文(easy version)_牛客练习赛141
G-三角符文(hard version)_牛客练习赛141
题意:有一个边长为 \(n\) 的的三角矩阵,第 \(i\) 行有 \(i\) 个字符,字符只有 \(a,b,c\),最后一行的 \(n\) 个字符已给出,第 \(i\) 行第 \(j\) 个字符按照以下方式确定:
若 \(x_{i+1,j}=x_{i+1,j+1}\),则 \(x_{i,j}=x_{i+1,j}\)。
若 \(x_{i+1,j}\neq x_{i+1,j+1}\),则 \(x_{i,j}\) 为和它们都不同的字符。
\(q\) 次询问,每次查询第 \(i\) 行第 \(j\) 个字符是什么。
题解:
令字符 \(a,b,c\) 分别为 \(0,1,2\),发现 \(x_{i,j}\) 满足 \(x_{i,j}\equiv -(x_{i+1,j}+x_{i+1,j+1})\bmod 3\)。
这是单次递推,尝试寻找多次递推:
并没有简化计算,尝试寻找合适的 \(d\) 使得能够加速计算,比如绝大多数组合数在 \(\bmod3\) 意义下为 \(0\)。
由卢卡斯定理可知,若 \(\binom{d}{k}\bmod3=0\),当且仅当在三进制下,并不是每一位都是 \(d_i\ge k_i\)。
如果取 \(d=3^m\),则 \(\forall 0<k<d\),都有 \(\binom{d}{k}\bmod3=0\)。
但是如果直接对询问三进制分解的话,若分解出 \(t=\log_3 n\) 个数,则需要 \(2^{t+1}-1\) 次递推,仍然无法接受。
考虑预处理一些行,预处理 \(L\) 行后,会分解出 \(\log_3 \frac{n}{L}\) 个数,需要 \(O((\frac{n}{L})^{\log_3 2})\) 次递推。取 \(L=729\) 可以通过。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define i128 __int128
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 5e5 + 5, M = 750;
int n, q;
short p[M][N];
char s[N];
int lg[N], Pow[N];
int get(int x, int y)
{
if(n - x + 1 <= 729) return p[n - x + 1][y];
int d = n - x;
return (6 - get(x + Pow[lg[d]], y) - get(x + Pow[lg[d]], y + Pow[lg[d]])) % 3;
}
void solve()
{
n = read(), q = read();
scanf(" %s", s + 1);
for(int i = 1; i <= n; ++i) p[1][i] = s[i] - 'a';
for(int i = 2; i <= min(n, 729); ++i)
for(int j = 1; j <= n - i + 1; ++j)
p[i][j] = (3 - p[i - 1][j] - p[i - 1][j + 1] + 3) % 3;
lg[0] = -1;
for(int i = 1; i <= n; ++i) lg[i] = lg[i / 3] + 1;
Pow[0] = 1;
for(int i = 1; Pow[i - 1] < n; ++i) Pow[i] = Pow[i - 1] * 3;
while(q--)
{
int x = read(), y = read();
int d = n - x + 1;
if(d <= 729) printf("%c", p[d][y] + 'a');
else
{
int ans = get(x, y);
printf("%c", ans + 'a');
}
}
}
int main()
{
int T = 1;
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号