2021NOIP模拟九总结
T1 图计数 40/100pts (根号分类讨论+整数分拆)
- 用\(m\)种颜色染一个n个点的每个联通块的图,求方案数
设有 ans 种这样的图,则答案为 \(m^{ans}\)
只需找出有多少种这样的图即可:发现这是个整数分拆问题
ans 为将 \(n\) 拆为若干正整数的方案数
朴素DP:\(O(n^2)\) 完全背包问题:40pts
优化:设 \(b=\sqrt n\),对于 \(<b\) 的物品,设 \(f(i,j)\) 表示 \(\le i\) 的,总和为 \(j\) 的方案数;转移:\(f(i,j)=f(i-1,j)+f(i,j-i)\)
对于 \(\ge b\) 的物品,出现次数必然 \(\le b\),设 \(g(i,j)\) 表示 \(i\) 个,总和为 \(j\) 的方案数;转移:\(g(i,j)=\sum\begin{cases}g(i-1,j-b)&选择一个b\\g(i,j-i)&将所有数加1\end{cases}\)
初始:\(f(0,0)=g(0,0)=1\),统计答案时:枚举两边的综合
时间复杂度:\(O(n\sqrt n)\)
点击查看代码
#include <math.h>
#include <stdio.h>
#include <string.h>
const int N = 2e5 + 5, mod = 999999599;
typedef long long LL;
int n, m, f[2][N], g[2][N], F[N], G[N];
int qpow(int b, int p) {
int res = 1;
while(p) {
if(p & 1) res = (LL)res * b % mod;
b = (LL)b * b % mod, p >>= 1;
}
return res;
}
int main() {
freopen("count.in", "r", stdin);
freopen("count.out", "w", stdout);
scanf("%d%d", &n, &m);
int b = sqrt(n);
F[0] = f[0][0] = 1;
for(int i = 1; i < b; i ++)
for(int j = 0; j <= n; j ++) {
f[i&1][j] = f[(i-1)&1][j];
if(j >= i) (f[i&1][j] += f[i&1][j-i]) %= (mod-1);
F[j] = f[i&1][j];
}
G[0] = g[0][0] = 1;
for(int i = 1; i * b <= n; i ++)
for(int j = 0; j <= n; j ++) {
g[i&1][j] = 0;
if(j >= b) g[i&1][j] += g[(i-1)&1][j-b];
if(j >= i) g[i&1][j] += g[i&1][j-i];
g[i&1][j] %= (mod-1);
(G[j] += g[i&1][j]) %= (mod-1);
}
int ans = 0;
for(int i = 0; i <= n; i ++)
ans = (ans + (LL)F[i] * G[n-i] % (mod-1)) % (mod-1);
printf("%d\n", qpow(m, ans));
fclose(stdin), fclose(stdout);
return 0;
}
T2 小w的作业 40/100pts
给出 \(n\le 5\times 10^4\) 个点,求所有 \(i<j\) 的 \(a_i\) 与 \(a_j\) 连线的辐角 (\(\in[0,\pi)\))的中位数(如果有偶数个,为两个的平均值)
只需实现函数 kth(LL k)
为找到第 \(k\) 大的角度
这可以使用二分解决:设 calc(double a)
表示计算角度在 \([0,a]\) 的点对
如果两个点 \((x_i,y_i)\),\((x_j,y_j)\) 满足 \(y_i<y_j\) 且 \(\frac{x_j-x_i}{y_j-y_i}\le t\) 其中 \(t=\tan(\frac{\pi}{2}-a)\) 则统计答案
\(\frac{x_j-x_i}{y_j-y_i}\le t\Leftrightarrow ty_j-ty_i\ge x_j-x_i \Leftrightarrow ty_j-x_j\ge ty_i-x_i\)
于是问题转化为 \((ty_i-x_i,y_i)\) 的顺序对问题
按双关键字排序,使用树状数组解决问题
时间复杂度:\(O(\log(\frac{\pi}{eps})n\log n)\)
点击查看代码
#include <math.h> // 使用二分找到第k大
#include <stdio.h> // 找第k大用二维偏序做
#include <string.h>
#include <algorithm>
typedef long long LL;
const double PI = acos(-1);
const int N = 5e4 + 5;
int n, tr[N], num[N], tot;
struct Pt {
int x, y, rnk; double val;
bool operator < (const Pt &a) const {
return fabs(val - a.val) < 1e-12 ? y < a.y : val < a.val;
}
} a[N]; // r为y的排名
inline void inc(int x) { for(; x <= tot; x += x & -x) ++ tr[x]; }
int query(int x) {
int res = 0;
for(; x; x -= x & -x) res += tr[x];
return res;
}
LL calc(double lim) {
double tmp = tan(PI / 2 - lim);
for(int i = 1; i <= tot; i ++) tr[i] = 0;
for(int i = 1; i <= n; i ++) a[i].val = a[i].x - a[i].y * tmp;
std::sort(a + 1, a + n + 1); LL res = 0; // 逆序对
for(int i = 1; i <= n; i ++) res += query(a[i].rnk), inc(a[i].rnk);
return res;
}
double kth(LL k) {
double l = 0, r = PI;
while(r - l > 1e-12) {
double mid = (l + r) / 2;
if(calc(mid) >= k) r = mid;
else l = mid;
}
return l;
}
int main() {
freopen("line.in", "r", stdin);
freopen("line.out", "w", stdout);
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%d%d", &a[i].x, &a[i].y), num[++ tot] = a[i].y;
std::sort(num + 1, num + tot + 1), tot = std::unique(num + 1, num + tot + 1) - num - 1;
for(int i = 1; i <= n; i ++) a[i].rnk = std::lower_bound(num + 1, num + tot + 1, a[i].y) - num;
LL m = n * LL(n - 1) / 2;
if(m & 1) printf("%.16lf\n", kth((m + 1) / 2));
else printf("%.16lf\n", (kth(m / 2) + kth(m / 2 + 1)) / 2);
return 0;
}
T3 BG的本性 20/100pts
将所有包子桶吃完的次数预处理出来,用结构体存\((day,id)\),将其双关键字排序
按顺序枚举这些包子桶,先将所有的整轮吃掉
剩下的用树状数组+二分解决等解决
点击查看代码
#include <stdio.h>
#include <algorithm>
typedef long long LL;
const int N = 1e5 + 5;
int n, m, w[N], a[N];
int tr[N];
void inc(int x) { for(; x <= n; x += x & -x) ++ tr[x]; }
void dec(int x) { for(; x <= n; x += x & -x) -- tr[x]; }
int query(int x) {
int res = 0;
for(; x; x -= x & -x) res += tr[x];
return res;
}
struct Dat {
int day, id;
bool operator < (const Dat &dat) {
return day < dat.day || (day < dat.day && id < dat.id);
}
} bao[N]; int x;
int main() {
freopen("foodie.in", "r", stdin);
freopen("foodie.out", "w", stdout);
scanf("%d%d%d", &n, &m, &x);
for(int i = 1; i <= n; i ++) scanf("%d", w + i);
for(int i = 1; i <= n; i ++) {
scanf("%d", a + i), bao[i].id = i;
if(w[i] <= x) bao[i].day = (x - w[i]) / a[i] + 1;
}
std::sort(bao + 1, bao + n + 1);
for(int i = 1; i <= n; i ++) inc(i);
int day = 0, pos = 1, tot = n;
// pos 为位置, tot为剩余的包子
for(int d = 1; d <= n; d ++) {
int tmp = bao[d].day - day;
while(pos + tmp > tot) {
int val = tot - pos + 1;
pos = 1, day += val, tmp -= val;
if(! -- m) return printf("%d\n", day), 0;
}
pos += tmp, day += tmp, tot --;
int q = query(bao[d].id);
if(q < pos) pos --;
dec(bao[d].id);
}
return printf("%d\n", day), 0;
}
T4 小w学图论 20/100pts
给出 \(n\) 点 \(m\) 边 无向图 \(G\),如果 \(G\) 中有边 \((x,y),(x,z)\) 则连边 \((y,z)\) 其中 \(x<y<z\) ,加边直到不能加为止
求生成的图给每个点染上 \(1\sim n\) 的颜色使得有边连接的点颜色不同
如果将生成的图求出来,则可以求出方案数:
将所有无向边改为从编号较小的节点连到编号较大的点的有向边,
答案为 \(\prod\limits_{u=1}^{n}(n-o_u)\) 其中 \(o_u\) 为 \(u\) 的出度
?如何求出每个点最终的出度?
对于每个点,维护一个集合 \(S_u\) ,初始为 \(\{u\}\cup\{u连出的点\}\)
于是每个 \(S_u\) 内的节点均构成一个团(两两连边)
从小到大枚举 \(u\),如果有 \(v\) 连到 \(u\)(一定 \(v<u\)),则 \(v\) 所在的集合中的点与 \(u\) 所在的集合可以合并为一个新的集合,这个集合为一个团
由于 \(u\) 被枚举出度后就没有贡献了,于是可以将 \(u\) 移除其所在集合了
点击查看代码
#include <set>
#include <stdio.h>
#include <string.h>
#include <algorithm>
const int N = 1e5 + 5, M = 2e5 + 5;
const int mod = 998244353;
typedef long long LL;
int n, m, fa[N]; std::set<int> set[N];
int h[N], e[M], nxt[M], idx;
void add(int a, int b) {
e[++ idx] = b, nxt[idx] = h[a], h[a] = idx;
}
int find(int x) {
if(x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
void merge(int u, int v) {
u = find(u), v = find(v);
if(u == v) return;
if(set[u].size() < set[v].size()) std::swap(u, v);
fa[v] = u; for(int t : set[v]) set[u].insert(t);
}
int main() {
freopen("graph.in", "r", stdin);
freopen("graph.out", "w", stdout);
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) fa[i] = i, set[i] = {i};
for(int i = 0, a, b; i < m; i ++) {
scanf("%d%d", &a, &b);
if(a > b) std::swap(a, b);
set[a].insert(b), add(b, a);
}
int res = 1;
for(int u = 1; u <= n; u ++) {
for(int i = h[u]; i; i = nxt[i]) merge(u, e[i]);
set[find(u)].erase(u);
res = (LL)res * (n - int(set[find(u)].size())) % mod;
}
printf("%d\n", res);
fclose(stdin), fclose(stdout);
return 0;
}