在列车上打的......A题一时不会做. TAT C题卡住了. 感觉是个数位DP, 但是拿什么作状态呢? 虽然数位和的所有可能值很少呢......跳过了这题, 发现后面是三道基本数据结构练习题. F没时间写了. 比赛时完成ABDE. C题最后用数位DP写出来了, 但是有更妙的方法. 题解: 6/6. # A. Treasure Hunt 给定x,y, 起点, 终点. 无限大的网格图上, 每次可以移动(+-x, +-y). 问是否能从起点到终点. 次数不限. (|坐标|,x,y≤10^5, x,y≥1)
也就是问 a(x,y) + b(x,-y) = (x',y') 这个方程是否有整数解. 算出表达式, 验证是否为整数.
typedef long long ll;
int main()
{
ll x1, y1, x2, y2, x, y;
cin >> x1 >> y1 >> x2 >> y2 >> x >> y;
x2 -= x1, y2 -= y1;
ll a = (x2*y + x*y2) / 2 / x / y, b = (x*y2 - x2*y) / 2 / x / y;
if (a*2*x*y == x2*y + x*y2 && b*2*x*y == x*y2 - x2*y)
puts("YES");
else
puts("NO");
return 0;
}
B. Makes And The Product
给出长度为n的正整数数组a. 问存在多少个三元组(i,j,k) (i<j<k) 使得a[i],a[j],a[k]的乘积取到最小值. (3≤n≤10^5, 1≤ai≤10^9)
显然取数组a的前3小. 这乘积都爆unsigned long long
了. 仔细一想, 我们并不需要知道乘积具体是多少. 它不能是其他3个数的乘积, 否则能被调整得更小. 排一遍序, 看看第3小的数能被哪些替换, 然后做一做乘法.
Tutorial给出了另一种做法. 处理出每个前/后缀的最小值, 和最小值出现的次数. 枚举j (即中间的下标).
typedef long long ll;
const int N = 1e5 + 5;
int a[N];
int main()
{
ios::sync_with_stdio(false);
int n;
cin >> n;
rep (i, 0, n) cin >> a[i];
sort(a, a+n);
int k = 3;
while (k < n && a[k] == a[2]) ++k;
if (a[0] == a[1] && a[1] == a[2])
{
cout << 1LL * k * (k-1) * (k-2) / 6 << endl;
}
else if (a[0] != a[1] && a[1] == a[2])
{
--k;
cout << 1LL * k * (k-1) / 2 << endl;
}
else
{
cout << k-2 << endl;
}
return 0;
}
C. Really Big Numbers
有多少个小于等于n的正整数x, 满足 x - x的十进制数位和 ≥ s? (1≤n,s≤10^18)
想做数位DP, 但这个状态不好定义啊......也许数位和所有取值的数量很少 (162个), 这个性质会有帮助?
后来想到, 枚举数位和sum, 并统计反面, 即≤ s
. 问题转化为, 有多少个小于 min(n, sum) 的数, 其十进制数位和等于 sum. 这就是经典问题啦.
但标签怎么是二分, 数学?
呃......其实是否满足条件是具有单调性的......式子列出来了, 但我没有关注这个......所以二分即可.
不用二分也可以. 由于数位和最大为162, 所以 s+162 一定是满足条件的数. 进一步地, [s+162,n] 都是. [1,s] 一定不是. 暴力检验 (s,s+162) 即可.
typedef long long ll;
ll f[19][10][164];
ll cal(ll s, ll X)
{
int x[19], n = 0, t = 0;
ll ret = 0;
while (X) x[n++] = X % 10, X /= 10;
per (i, n-1, 0)
{
ret += x[i] && s >= t ? f[i][x[i]-1][s-t] : 0;
t += x[i];
}
return ret;
}
int main()
{
ios::sync_with_stdio(false);
rep (j, 0, 10) rep (k, 0, 10)
f[0][j][k] = (j == k) + (j ? f[0][j-1][k] : 0);
rep (i, 1, 19) rep (j, 0, 10) rep (k, 0, 164)
f[i][j][k] = (k >= j ? f[i-1][9][k-j] : 0) + (j ? f[i][j-1][k] : 0);
ll n, s, ans = 0;
cin >> n >> s;
rep (i, 1, 164)
ans += cal(i, min(n+1, s+i));
cout << n - ans << endl;
return 0;
}
D. Imbalanced Array
一个长度为n的数组a. 求所有子区间的最大值-最小值之和. (1≤n≤10^6, 1≤ai≤10^6)
把最大值和最小值拆开来求. 考虑每个值的贡献, 即, 求出每个值作为最大值和最小值的区间. 这是单调栈的经典应用.
有一个小问题. a中元素可能重复. 我简单粗暴地把数改成 (数,位置), 这样元素就是唯一的了. 这样等价于求出每个元素作为最左最小值, 最右最大值的区间.
有一个小trick可以简化实现. 先求最小值区间. 每个元素取反, 再求最小值区间, 即原数组的最大值区间.
E. Choosing The Commander
维护一个可重集, 支持: - 插入一个正整数 - 删除一个集合中的数 - 给定y,z, 求集合中有多少个数x满足 x xor y < z.
(1≤操作数q≤10^5, 正整数≤10^8)
用 0-1 Trie 树 维护即可. 询问在树上二分. 是否加上另一棵子树的值, 取决于这一位是否为1. [bzoj 4103] [Thu Summer Camp 2015]异或运算 我的TLE做法就是这个......大概是经典问题.
const int V = 28 * 1e5;
struct Trie
{
int ptr, root, ch[V][2], v[V];
void modify(int& o, int s, int k, int a)
{
if (!o) o = ++ptr;
v[o] += a;
if (k >= 0) modify(ch[o][s>>k & 1], s, k-1, a);
}
int query(int o, int p, int l, int k)
{
if (!o || k < 0) return 0;
int a = p>>k & 1, b = l>>k & 1, s = 0;
if (b)
s = v[ch[o][a]] + query(ch[o][a^1], p, l, k-1);
else
s = query(ch[o][a], p, l, k-1);
return s;
}
} T;
int main()
{
ios::sync_with_stdio(false);
int q;
cin >> q;
while (q--)
{
int o, p, l;
cin >> o >> p;
if (o <= 2)
{
T.modify(T.root, p, 27, o == 1 ? 1 : -1);
}
else
{
cin >> l;
cout << T.query(T.root, p, l, 27) << endl;
}
}
return 0;
}
F. MEX Queries
维护一个初始为空的集合, n个操作: - 添加[l,r]中的所有整数 - 删除[l,r]中的所有整数 (如果存在) - 翻转[l,r]中所有整数的存在性
每个操作结束后, 输出集合中没出现的最小正整数. (1≤n≤10^5, 1≤l≤r≤10^18)
线段树就可以实现? 一看数的范围, 10^18......
不过我们可以直接对值域开线段树, 动态加点. 或者, 因为可以离线, 所以离散化一下. 我选择了后者, 粗暴地把x-1,x,x+1都加了进去. 然后, 两种 lazy tag. 下传是容易的, 我们可以保证每个节点每一时刻只有一种标记. 别忘了加入1.
事实上, 对于[l,r], 只用加入l和r+1. 因为, 对于操作相同的一个区间, 我们关心的最小值.
#define NDEBUG
#include <bits/stdc++.h>
// #define rep per
#define ALL 1, 0, top-1
#define LEFT o*2, l, m
#define RIGHT o*2+1, m+1, r
using namespace std;
typedef long long ll;
const int N = 6 * 1e5 + 1;
struct Seg
{
int cnt[N*4];
short cov[N*4];
bool inv[N*4];
void up(int o)
{
cnt[o] = cnt[o*2] + cnt[o*2 + 1];
}
void down_cover(int o, int w, short v)
{
inv[o] = 0;
cov[o] = v;
cnt[o] = v > 0 ? w : 0;
}
void down_inverse(int o, int w)
{
if (cov[o])
{
cov[o] = cov[o] > 0 ? -1 : 1;
cnt[o] = cov[o] > 0 ? w : 0;
}
else
{
inv[o] ^= 1;
cnt[o] = w - cnt[o];
}
}
void down(int o, int w)
{
int r = w/2, l = w-r;
if (cov[o])
{
down_cover(o*2, l, cov[o]);
down_cover(o*2+1, r, cov[o]);
cov[o] = 0;
}
else if (inv[o])
{
down_inverse(o*2, l);
down_inverse(o*2+1, r);
inv[o] = 0;
}
}
void set(int L, int R, short v, int o, int l, int r)
{
if (L <= l && r <= R) return down_cover(o, r-l+1, v);
down(o, r-l+1);
int m = (l+r)/2;
if (L <= m) set(L, R, v, LEFT);
if (R > m) set(L, R, v, RIGHT);
up(o);
}
void invert(int L, int R, int o, int l, int r)
{
if (L <= l && r <= R) return down_inverse(o, r-l+1);
down(o, r-l+1);
int m = (l+r)/2;
if (L <= m) invert(L, R, LEFT);
if (R > m) invert(L, R, RIGHT);
up(o);
}
int query(int o, int l, int r)
{
if (l == r) return assert(!cnt[o]), l;
down(o, r-l+1);
int m = (l+r)/2;
return cnt[o*2] < m-l+1 ? query(LEFT) : query(RIGHT);
}
} T;
struct Op
{
int o;
ll l, r;
} Q[N/6];
int top;
ll h[N];
inline void add(ll x)
{
h[top++] = x;
h[top++] = x+1;
if (x > 1) h[top++] = x-1;
}
int main()
{
ios::sync_with_stdio(false);
int n;
cin >> n;
h[top++] = 1;
rep (i, 0, n)
{
cin >> Q[i].o >> Q[i].l >> Q[i].r;
add(Q[i].l);
add(Q[i].r);
}
sort(h, h+top);
top = unique(h, h+top) - h;
rep (i, 0, n)
{
Op& q = Q[i];
int l = lower_bound(h, h+top, q.l) - h, r = lower_bound(h, h+top, q.r) - h;
if (q.o <= 2) T.set(l, r, q.o == 1 ? 1 : -1, ALL);
else T.invert(l, r, ALL);
cout << h[T.query(ALL)] << endl;
}
return 0;
}