「联赛测试」肆拾肆
long long ago,I remember someone set a flag...
树
什么都能做,如果你非得树剖加一个 \(log\),应该也能过。
不过倍增显然更好处理。
看题目有一个特别好的一个点来:每次询问的 \(v\) 保证是 \(u\) 的祖先。
所以预处理一个倍增数组和一个 \(val\) 数组,\(val\) 用来存这个点走到根节点需要”努力学习“的次数。
每次询问查询 \(u_i\) 之上第一个比 \(c_i\) 大的点 \(x\) 。
如果 \(deep[x] < deep[v]\),显然是 \(0\) 。
否则求出 \(u\) 到 \(v\) 之间的最大值 \(tmp\),查询 \(v_i\) 之上第一个比 \(tmp\) 大的点 \(y\),最后的答案是 \(val[x] - val[y]\) 。
自我感觉写麻烦了
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;
inline int read () {
register int x = 0, w = 1;
register char ch = getchar ();
for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
return x * w;
}
inline void write (register int x) {
if (x / 10) write (x / 10);
putchar (x % 10 + '0');
}
int n, q;
int w[maxn], val[maxn];
struct Edge {
int to, next;
} e[maxn << 1];
int tot, head[maxn];
inline void Add (register int u, register int v) {
e[++ tot].to = v;
e[tot].next = head[u];
head[u] = tot;
}
int deep[maxn];
int f[maxn][21], maxx[maxn][21];
inline void DFS0 (register int u, register int fa) {
deep[u] = deep[fa] + 1, maxx[u][0] = w[u];
for (register int i = 1; (1 << i) <= deep[u]; i ++) {
f[u][i] = f[f[u][i - 1]][i - 1];
maxx[u][i] = max (maxx[u][i - 1], maxx[f[u][i - 1]][i - 1]);
}
for (register int i = head[u]; i; i = e[i].next) {
register int v = e[i].to;
if (v == fa) continue;
f[v][0] = u, DFS0 (v, u);
}
}
inline int Find (register int u, register int c) {
for (register int i = 20; i >= 0; i --) {
if (maxx[u][i] <= c) u = f[u][i];
}
return u;
}
inline int Getmax (register int u, register int v) {
register int maxval = 0;
for (register int i = 20; i >= 0; i --) {
if (deep[f[u][i]] >= deep[v]) maxval = max (maxval, maxx[u][i]), u = f[u][i];
}
return max (maxval, w[v]);
}
inline void DFS1 (register int u, register int fa) {
val[u] = val[Find (u, w[u])] + 1;
for (register int i = head[u]; i; i = e[i].next) {
register int v = e[i].to;
if (v == fa) continue;
DFS1 (v, u);
}
}
int main () {
freopen ("tree.in", "r", stdin);
freopen ("tree.out", "w", stdout);
n = read(), q = read(), memset (maxx, 0x3f, sizeof maxx);
for (register int i = 1; i <= n; i ++) w[i] = read();
for (register int i = 1; i <= n - 1; i ++) {
register int u = read(), v = read();
Add (u, v), Add (v, u);
}
DFS0 (1, 0), DFS1 (1, 0);
while (q --) {
register int u = read(), v = read(), c = read();
register int x = Find (u, c);
if (deep[x] < deep[v]) {
puts ("0");
} else {
register int tmp = Getmax (u, v);
register int y = Find (v, tmp);
printf ("%d\n", val[x] - val[y]);
}
}
return 0;
}
环 circle
显然我们要求的是最小环的数量,而且因为这些环在一个完全图中,所有的非三元环都会被分解成三元环(画图易证),所以这些环只能是三元环。
问题转化成了求三元环的期望数。
如果没有限制,显然答案是从 \(n\) 个点中选 \(3\) 个点:
用容斥减去不合法的情况,对于一个三元图有 \(8\) 种情况:
图
容易发现,对于一个不合法的三元环,总是有一个点的出度为 \(2\) 。
可以推得:对于一个三元组 \((a,b,c)\),如果 \(b\in p_a\;且\;c\in p_a\;且\;b\neq c\)(\(p_a\) 表示 \(a\) 出边到达的点的集合),则这个三元组不是三元环。
考虑对每个点 \(i\) 减去上面的贡献,设 \(a_i\) 表示 \(i\) 确定出边的数量,\(b_i\) 表示 \(i\) 除去确定连的边还要连的边,则每次减去的期望是:
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
typedef long long ll;
using namespace std;
const int maxn = 1e5 + 50, INF = 0x3f3f3f3f, mod = 1e9 + 7;
inline int read () {
register int x = 0, w = 1;
register char ch = getchar ();
for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
return x * w;
}
inline void write (register int x) {
if (x / 10) write (x / 10);
putchar (x % 10 + '0');
}
int n, m, deg[maxn], a[maxn], b[maxn];
ll ans;
inline ll qpow (register ll a, register ll b) {
register ll ans = 1;
while (b) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod, b >>= 1;
}
return ans;
}
int main () {
freopen ("circle.in", "r", stdin);
freopen ("circle.out", "w", stdout);
n = read(), m = read(), ans = 1ll * n * (n - 1) % mod * (n - 2) % mod * qpow (6, mod - 2) % mod;
for (register int i = 1; i <= m; i ++) {
register int u = read(), v = read(); deg[u] ++, deg[v] ++, a[u] ++;
}
for (register int i = 1; i <= n; i ++) {
b[i] = n - 1 - deg[i];
ans = ((ans - 1ll * a[i] * (a[i] - 1) % mod * qpow (2, mod - 2) % mod - 1ll * a[i] * b[i] % mod * qpow (2, mod - 2) % mod - 1ll * b[i] * (b[i] - 1) % mod * qpow (2, mod - 2) % mod * qpow (4, mod - 2) % mod) % mod + mod) % mod;
}
printf ("%lld\n", ans);
return 0;
}
礼物 gift
一个转化式:
原式可以理解为从 \((-a_i,-b_i)\) 走到 \((a_j,b_j)\) 的方案数。
然后,按 \(y=-x\) 将两边分开,可以转化为从 \((-a_i,-b_i)\) 横着走 \(t\) 步到 \(y=-x\) 上,再横着走 \(a_i+a_j-t\) 步到 \((a_j,b_j)\) 。
根据式子发现,\(t\) 只用枚举 \(-a_i\) 到 \(b_i\)。
考虑实现:
我们可以将每个 \(i\),用某个 \(t\) 求出前半部分的值,再乘上对应的 \(t\) 的 \(1\sim i-1\) 的后半部分的值的总和,最后的答案乘上 \(2\) 即可。
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 50, INF = 0x3f3f3f3f, mod = 1e9 + 7, base = 2e7;
inline int read () {
register int x = 0, w = 1;
register char ch = getchar ();
for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
return x * w;
}
inline void write (register int x) {
if (x / 10) write (x / 10);
putchar (x % 10 + '0');
}
int n, maxx, ans;
int a[maxn], b[maxn];
int fac[20000005], facinv[20000005], val[40000005];
inline int qpow (register int a, register int b) {
register int ans = 1;
while (b) {
if (b & 1) ans = 1ll * ans * a % mod;
a = 1ll * a * a % mod, b >>= 1;
}
return ans;
}
inline void Init () {
fac[0] = 1;
for (register int i = 1; i <= maxx; i ++)
fac[i] = 1ll * fac[i - 1] * i % mod;
facinv[maxx] = qpow (fac[maxx], mod - 2);
for (register int i = maxx; i >= 1; i --)
facinv[i - 1] = 1ll * facinv[i] * i % mod;
}
inline int C (register int a, register int b) {
if (a == 0 || b == 0 || a == b) return 1;
if (a < 0 || b < 0 || a < b) return 0;
return 1ll * fac[a] * facinv[b] % mod * facinv[a - b] % mod;
}
int main () {
freopen ("gift.in", "r", stdin);
freopen ("gift.out", "w", stdout);
n = read();
for (register int i = 1; i <= n; i ++)
a[i] = read(), b[i] = read(), maxx = max (maxx, a[i] + b[i]);
Init ();
for (register int i = 1; i <= n; i ++) {
for (register int t = -a[i]; t <= b[i]; t ++)
ans = (ans + 1ll * C (a[i] + b[i], a[i] + t) * val[t + base] % mod) % mod;
for (register int t = -b[i]; t <= a[i]; t ++)
val[t + base] = (val[t + base] + C (a[i] + b[i], a[i] - t)) % mod;
}
printf ("%lld\n", 2ll * ans % mod);
return 0;
}
最优排名
考虑贪心取,我们取 \(v\) 比当前的 \(v_1\) 大的所有元素中 \(w_i-v_i\) 最小的,每次 \(v_1\) 减小的时候,用单调指针将后面 \(v_i\) 比它大的元素放到堆里。
会发现,我们每次贪心只会造成局部最优,并不能保证全局最优,所以每次操作的时候取个 \(min\) 即可。
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
typedef long long ll;
using namespace std;
const int maxn = 3e5 + 50, INF = 0x3f3f3f3f;
inline ll read () {
register ll x = 0, w = 1;
register char ch = getchar ();
for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
return x * w;
}
inline void write (register int x) {
if (x / 10) write (x / 10);
putchar (x % 10 + '0');
}
int n, r = 1, ans, num;
ll s;
struct Node {
ll v, w;
inline bool operator < (const Node &x) const { return v > x.v; }
} a[maxn];
priority_queue <ll, vector <ll>, greater <ll> > q;
int main () {
freopen ("rank.in", "r", stdin);
freopen ("rank.out", "w", stdout);
n = read() - 1, s = read(), read();
for (register int i = 1; i <= n; i ++) {
a[i].v = read(), a[i].w = read();
if (a[i].v <= s) ans ++;
}
sort (a + 1, a + n + 1);
for (register int i = 1; i <= n; i ++, r = i) {
if (a[i].v <= s) break;
q.push (a[i].w - a[i].v + 1);
}
while (! q.empty ()) {
register ll u = q.top ();
if (s < u) break;
q.pop (), s -= u, num ++;
while (a[r].v > s && r <= n) q.push (a[r].w - a[r].v + 1), r ++;
ans = max (ans, num + n - r + 1);
}
printf ("%d\n", n + 1 - ans);
return 0;
}