校赛拿了亚军,做出了7道题目,还行
接下来一段时间就是补题
不仅补校赛的题,还补相关知识点的题目
比如接下来搞一搞dfs序,数学
发现这两个部分之前都总结过哈哈,但时间太久遗忘了
搞起
12.21周一
dfs序,搞起
dfs序可以给每个节点一个id从而把树形结构转化为线性结构。
一颗子树在dfs序上就是一个区间
l[id]是节点的id,而l[id]到r[id]就是以l为根的子树的dfs序
所以可以用来判断子树,子树问题可以用树状数组线段树来搞
poj 3321
用树状数组维护就好
但我用vector超时了,要用链式前向星
这个知识点已经遗忘了……
#include<cstdio>
#include<vector>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 1e5 + 10;
vector<int> g[MAXN];
int f[MAXN], a[MAXN], n, m;
int l[MAXN], r[MAXN], id;
int lowbit(int x) { return x & (-x); }
void add(int x, int w)
{
while(x <= n)
{
f[x] += w;
x += lowbit(x);
}
}
int sum(int x)
{
int res = 0;
while(x)
{
res += f[x];
x -= lowbit(x);
}
return res;
}
void dfs(int u, int fa)
{
l[u] = ++id;
REP(i, 0, g[u].size())
{
int v = g[u][i];
if(v == fa) continue;
dfs(v, u);
}
r[u] = id;
}
int main()
{
scanf("%d", &n);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
_for(i, 1, n) add(i, 1), a[i] = 1;
dfs(1, -1);
scanf("%d", &m);
while(m--)
{
char s[5]; int x;
scanf("%s%d", s, &x);
if(s[0] == 'Q') printf("%d\n", sum(r[x]) - sum(l[x]-1));
else
{
int id = l[x];
if(a[id]) a[id] = 0, add(id, -1);
else a[id] = 1, add(id, 1);
}
}
return 0;
}
Problem L. 代代宗师
校赛补题
题意是每个点有权值1或0,询问以i为根节点的子树中深度为h的结点的权值和
这道题用了dfs序,我已经遗忘了这个知识点,所以当时根本没法做,不知道怎么判断i的子树内的节点
做法是每个高度都把节点的dfs序存进去,同时算出前缀和
询问时在这个高度中,一个子树的节点一定是连续的一段,因为是按照dfs序的方式进入数组的
因此就可以用二分来找出这一段序列的左右两个端点
这里有个细节卡了我一下,注意根节点的dfs序范围和子节点范围两端可能重合可能不重合
如果是本身那就都重合
如果不是本身,左端点不重合,右端点可能重合可能不重合
因此左端点用lower_bound
右端点用upper_bound - 1
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 1e5 + 10;
vector<int> g[MAXN], d[MAXN], s[MAXN];
int l[MAXN], r[MAXN], a[MAXN], n, q, id;
void dfs(int u, int fa, int dep)
{
l[u] = ++id;
d[dep].push_back(l[u]);
if(!s[dep].size()) s[dep].push_back(a[u]);
else s[dep].push_back(a[u] + s[dep][s[dep].size()-1]);
REP(i, 0, g[u].size())
{
int v = g[u][i];
if(v == fa) continue;
dfs(v, u, dep + 1);
}
r[u] = id;
}
int main()
{
scanf("%d%d", &n, &q);
_for(i, 1, n) scanf("%d", &a[i]);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, -1, 1);
while(q--)
{
int v, h;
scanf("%d%d", &v, &h);
int L = lower_bound(d[h].begin(), d[h].end(), l[v]) - d[h].begin();
int R = upper_bound(d[h].begin(), d[h].end(), r[v]) - d[h].begin() - 1;
if(L == 0) printf("%d\n", s[h][R]);
else printf("%d\n", s[h][R] - s[h][L-1]);
}
return 0;
}
12.22周二
继续补题
Problem M. yxq 决斗
由于 yxq 打架屡战屡胜,他已经不满足打架取胜所带来的快感,因此他转向了决斗:to be or not to be! yxq 和别人决斗,初始 yxq 血条为 a,别人血条为 b。决斗为回合制,每回合 yxq 爆头对方 的机率为 p,被对方爆头的机率为 (1 − p),被爆头者掉 1 滴血,当一方血条为 0 时,则其阵亡, 请问 yxq 活下来的概率是多少,你需要输出模 998244353 意义下的结果。
多组测试数据第一行输入一个正整数 T,表示数据组数,T ≤ 10. 每组数据输入三个正整数 a,b,p,意义见题面,p 为模 998244353 意义下的数,a, b ≤ 1000
这题当时是写出来了。听了题解后发现有其他做法,学习学习。
dp做法
这个数据范围是一千,所以可以用n方做法
可以用dp来解决问题。这里都是模p意义下的,就不用浮点数了,都是整数
有些细节要注意
(1)转移时越界的时候看作0,即这种状态不存在。故要每次初始化为0
(2)当一直要取MOD的时候可以两个加和乘的函数,变得很方便
(3)这题有个坑就是最后一次一定是我方打的,不能把对方打的也算进去
所以转移时要特判一下
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MOD = 998244353;
const int MAXN = 1e3 + 10;
int dp[MAXN][MAXN], p, a, b;
int add(int x, int y) { return (x + y) % MOD; }
int mul(int x, int y) { return 1ll * x * y % MOD; }
int main()
{
int T; scanf("%d", &T);
while(T--)
{
memset(dp, 0, sizeof(dp));
int ans = 0;
scanf("%d%d%d", &a, &b, &p);
for(int i = a; i >= 0; i--)
for(int j = b; j >= 0; j--)
{
if(i == a && j == b)
{
dp[a][b] = 1;
continue;
}
if(j == 0) dp[i][j] = mul(dp[i][j+1], p);
else dp[i][j] = add(mul(dp[i+1][j], 1 - p + MOD), mul(dp[i][j+1], p));
if(j == 0 && i > 0) ans = add(ans, dp[i][j]);
}
printf("%d\n", ans);
}
return 0;
}
数学做法
就是高中数学推公式就行
注意一定最后一次是我方击杀
这里涉及到组合数
考试的时候是初始化阶乘来算的
现在换另一种方式,用递推初始化组合数
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MOD = 998244353;
const int MAXN = 1e3 + 10;
int dp[MAXN][MAXN], p, a, b;
int c[MAXN][MAXN << 1];
int add(int x, int y) { return (x + y) % MOD; }
int mul(int x, int y) { return 1ll * x * y % MOD; }
void init()
{
_for(j, 0, 2000) c[0][j] = 1;
_for(i, 1, 1000)
_for(j, 1, 2000)
c[i][j] = add(c[i][j-1], c[i-1][j-1]);
}
int binpow(int a, int b)
{
int res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = mul(res, a);
a = mul(a, a);
}
return res;
}
int main()
{
init();
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d%d", &a, &b, &p);
int sum = 0;
_for(k, 0, a - 1)
sum = add(sum, mul(c[k][b-1+k], binpow(1-p+MOD, k)));
printf("%d\n", mul(binpow(p, b), sum));
}
return 0;
}
Problem H. 2020
求这玩意
当时一脸懵逼
首先一段的和可以转化为1到k的和,然后相减即可
这是个很常见的思路
后来了解到这可以用拉格朗日插值法
给n个点,求过这n个点的曲线
多项式函数,显然可以代入点解方程
但拉格朗日插值法可以解决这个问题,非常秀
这个自行百度吧,百度可以看懂
至于这道题,显然是推求和公式
如果是1次方,那么求和后是n(n + 1)/2,是个二次多项式
那么4次方,求和后就是个5次多项式
那么这里就可以用拉格朗日插值法
取x为0, 1,2, 3, 4, 5六个点,就可以列出方程,代入题目给的点就行了
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MOD = 1e9 + 7;
int F[10];
int add(int a, int b) { return (a + b) % MOD; }
int mul(int a, int b) { return 1ll * a * b % MOD; }
void init()
{
_for(i, 1, 5)
F[i] = add(F[i-1], pow(i, 4));
}
int binpow(int a, int b)
{
int res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = mul(res, a);
a = mul(a, a);
}
return res;
}
int inv(int x) { return binpow(x, MOD - 2); }
int f(int n)
{
int res = 0;
_for(i, 0, 5)
{
int now = F[i];
_for(j, 0, 5)
if(j != i)
now = mul(now, n - j + MOD);
_for(j, 0, 5)
if(j != i)
now = mul(now, inv(i - j + MOD));
res = add(res, now);
}
return res;
}
int main()
{
init();
int T; scanf("%d", &T);
while(T--)
{
int n; scanf("%d", &n);
int ans = f(n + 2020) - f(2019);
if(ans < 0) ans += MOD; //涉及到减法可能为负,小细节注意一下
printf("%d\n", ans);
}
return 0;
}
12.23 周三
校赛有一道数学题没做出来,今天弄质数和约数部分,加油
复习线性筛
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 100 + 10;
bool is_prime[MAXN];
vector<int> prime;
void get_prime()
{
memset(is_prime, true, sizeof(is_prime));
is_prime[0] = is_prime[1] = false;
REP(i, 2, MAXN)
if(is_prime[i])
{
prime.push_back(i);
for(int j = i; j < MAXN; j += i)
is_prime[j] = false;
}
}
int main()
{
get_prime();
REP(i, 0, prime.size())
printf("%d ", prime[i]);
return 0;
}
Problem I . 「一は全、全は一」
当时没啥思路
后来听了题解,感觉很秀
把因数d化成n/a
把式子列一下发现分母可以配对成n
因数个数为k
答案就是n^(k/2)
当然如果是完全平方数,那就k是奇数,要多乘一个根号n
问题是怎么快速求k
暴力会超时
这里用质因数分解,因数个数为(a1+1)(a2+1)(a3+1)……
因此要初始化质数,然后质因数分解,然后算出k
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MOD = 1e9 + 7;
const int MAXN = 1e5 + 10;
bool is_prime[MAXN];
vector<int> prime;
int add(int a, int b) { return (a + b) % MOD; }
int mul(int a, int b) { return 1ll * a * b % MOD; }
int binpow(int a, int b)
{
int res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = mul(res, a);
a = mul(a, a);
}
return res;
}
void get_prime()
{
memset(is_prime, true, sizeof(is_prime));
is_prime[0] = is_prime[1] = false;
REP(i, 2, MAXN)
if(is_prime[i])
{
prime.push_back(i);
for(int j = i; j < MAXN; j += i)
is_prime[j] = false;
}
}
int num(int n)
{
int res = 1;
REP(i, 0, prime.size())
if(n % prime[i] == 0)
{
int cnt = 0;
while(n % prime[i] == 0)
{
n /= prime[i];
cnt++;
}
res *= (cnt + 1);
if(n == 1) break;
}
if(n > 1) res *= 2;
return res;
}
int main()
{
get_prime();
int T; scanf("%d", &T);
while(T--)
{
int n, t; scanf("%d", &n);
t = num(n);
if(t & 1) printf("%d\n", mul(binpow(n, t / 2), sqrt(n)));
else printf("%d\n", binpow(n, t / 2));
}
return 0;
}
NOIP2009 Hankson 的趣味题
就是考枚举因子,可以暴力
可以手算一下,逼近极限了
10的七次方到10的八次方
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
int a0, a1, b0, b1;
int gcd(int a, int b) { return !b ? a : gcd(b, a % b); }
int lcm(int a, int b) { return a / gcd(a, b) * b; }
int check(int x)
{
return gcd(x, a0) == a1 && lcm(x, b0) == b1;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d%d%d", &a0, &a1, &b0, &b1);
int ans = 0;
for(int x = 1; x * x <= b1; x++)
if(b1 % x == 0)
{
if(x * x == b1) ans += check(x); //完全平方数一般要特判一下,是个坑
else ans += check(x) + check(b1 / x);
}
printf("%d\n", ans);
}
return 0;
}
枚举因子还可以优化
先质因数分解,然后dfs枚举出所有因子
优化效果挺明显的
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 1e5 + 10;
vector<int> prime, ve;
bool is_prime[MAXN];
int a0, a1, b0, b1;
int a[15], cnt, ans;
int gcd(int a, int b) { return !b ? a : gcd(b, a % b); }
int lcm(int a, int b) { return a / gcd(a, b) * b; }
int check(int x)
{
return gcd(x, a0) == a1 && lcm(x, b0) == b1;
}
void get_prime()
{
memset(is_prime, true, sizeof(is_prime));
is_prime[0] = is_prime[1] = false;
REP(i, 2, MAXN)
if(is_prime[i])
{
prime.push_back(i);
for(int j = i; j < MAXN; j += i)
is_prime[j] = false;
}
}
void init()
{
int t = b1;
ve.clear(); cnt = ans = 0;
REP(i, 0, prime.size())
if(t % prime[i] == 0)
{
ve.push_back(prime[i]);
a[cnt] = 0;
while(t % prime[i] == 0)
{
t /= prime[i];
a[cnt]++;
}
cnt++;
if(t == 1) break;
}
if(t > 1) a[cnt++] = 1, ve.push_back(t);
}
void dfs(int i, int now)
{
if(i == cnt)
{
ans += check(now);
return;
}
_for(j, 0, a[i])
{
int t = 1;
_for(k, 1, j) t *= ve[i];
dfs(i + 1, now * t);
}
}
int main()
{
get_prime();
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d%d%d", &a0, &a1, &b0, &b1);
init();
dfs(0, 1);
printf("%d\n", ans);
}
return 0;
}
对顶堆复习
可以动态维护第k大的值
复杂度logn
学算法关键是理解思想,这样就容易写出来了,而不是死背模板
对顶堆的思想很简单,就是从分界点分出两个堆,维护这两个堆就好
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
priority_queue<int> q1;
priority_queue<int, vector<int>, greater<int> > q2;
void add(int x)
{
if(!q2.size() || x > q2.top()) q2.push(x);
else q1.push(x);
if(q1.size() > q2.size() + 1) q2.push(q1.top()), q1.pop();
if(q2.size() > q1.size() + 1) q1.push(q2.top()), q2.pop();
}
int work()
{
if(q1.size() > q2.size()) return q1.top();
return q2.top();
}
int main()
{
int n; scanf("%d", &n);
_for(i, 1, n)
{
int x; scanf("%d", &x);
add(x);
if(i & 1) printf("## %d\n", work());
}
return 0;
}
复习nth_element
有些题这个函数很有用
可以把第k小的数放到第k个位置,左边的小于等于它,右边的大于等于大
要变成第k大用greater<int>
复杂度O(n)
比如计算中需要前k个最小的数,就可以用这个函数,比排序nlogn要优
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
int main()
{
int a[] = {0, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
nth_element(a + 1, a + 3, a + 11, greater<int>()); //起始,第几个数,末尾加1
_for(i, 0, 10) printf("%d ", a[i]); puts("");
return 0;
}
Problem D. YYGQ
这道题的难点在于怎么删去
我考试时写了链表,但一直WA,链表不好写,容易写错
正解是用栈,栈弹出元素就是删去
栈手写就好,比stl能实现更多功能
然后我因为strlen这个函数卡了很久
巨坑函数
(1)每次调用strlen都是O(n),我把这个函数写到循环里面了,就多调用了很多次导致超时
所以应该用一个变量把strlen存一下
所以注意strlen用多次的时候用变量存起来
(2)strlen的返回值为unsigned int,没有负数
所以比较的时候要注意这一点,可以强制转化成int
很多stl的size貌似也是这样
所以strlen注意一点,最好用一个int变量存一下
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 1e6 + 10;
char s[MAXN], k[MAXN];
int cnt;
bool check(char str[])
{
int len = strlen(str);
if(cnt < len) return false;
for(int i = len - 1, p = cnt; i >= 0; i--, p--)
if(str[i] != k[p]) return false;
cnt -= len;
return true;
}
int main()
{
scanf("%s", s);
int len = strlen(s);
REP(i, 0, len)
{
k[++cnt] = s[i];
check("jl") || check("bhb") || check("jz");
}
_for(i, 1, cnt) putchar(k[i]);
if(!cnt) puts("NULL");
return 0;
}
复习矩阵快速幂
校赛考了一道
其实我早已经忘记代码怎么写了,但我记得思想
于是就写出来了
所以说学算法一定要深刻理解思想,才会流畅写得出来,而不是死记硬背代码
Fibonacci 第 n 项
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
int x, MOD;
struct node
{
int c[5][5];
node() { memset(c, 0, sizeof(c)); }
};
int add(int a, int b) { return (a + b) % MOD; }
int Mul(int a, int b) { return 1ll * a * b % MOD; }
node mul(node a, node b)
{
node res;
_for(i, 1, 2)
_for(j, 1, 2)
_for(k, 1, 2)
res.c[i][j] = add(res.c[i][j], Mul(a.c[i][k], b.c[k][j]));
return res;
}
node binpow(node a, int b)
{
node res;
res.c[1][1] = res.c[2][2] = 1;
for(; b; b >>= 1)
{
if(b & 1) res = mul(res, a);
a = mul(a, a);
}
return res;
}
int main()
{
scanf("%d%d", &x, &MOD);
node a;
a.c[1][1] = a.c[1][2] = a.c[2][1] = 1;
a = binpow(a, x - 1);
printf("%d\n", a.c[1][1]);
return 0;
}
Fibonacci 前 n 项和
手算矩阵即可,多加一项和的
记得可以手算个几项检验正确性
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
int x, MOD;
struct node
{
int c[5][5];
node() { memset(c, 0, sizeof(c)); }
};
int add(int a, int b) { return (a + b) % MOD; }
int Mul(int a, int b) { return 1ll * a * b % MOD; }
node mul(node a, node b)
{
node res;
_for(i, 1, 3)
_for(j, 1, 3)
_for(k, 1, 3)
res.c[i][j] = add(res.c[i][j], Mul(a.c[i][k], b.c[k][j]));
return res;
}
node binpow(node a, int b)
{
node res;
res.c[1][1] = res.c[2][2] = res.c[3][3] = 1;
for(; b; b >>= 1)
{
if(b & 1) res = mul(res, a);
a = mul(a, a);
}
return res;
}
int main()
{
scanf("%d%d", &x, &MOD);
node a;
a.c[1][1] = a.c[2][1] = a.c[2][2] = 1;
a.c[2][3] = a.c[3][2] = 1;
a = binpow(a, x - 1);
printf("%d\n", add(a.c[1][1], add(a.c[2][1], a.c[3][1])));
return 0;
}
12.24 周四
今晚平安夜和朋友出去玩,也比较疲惫,今天就不训练了
12.25 周五
圣诞节去玩了
12.26 周六
前两天一来圣诞节,二来也比较疲惫,就放松休息了
今天感觉状态又恢复过来了,搞起
Problem K. 撒播芙兰三三的离别花瓣
这道题思维量很大,代码很简单
保持好习惯,一道不会的题尽量独立思考
问题是怎么表示这个(r1, c1), (r1, c2), (r2, c1)都有时,(r2, c2)也有
这里要把它抽象成一个图,把点看成一条边
发现成为一个联通分量后,就等价于多了一条边,也就可以符合题目说的这种条件。
所以一个点就是一个边,最后就成了很多联通分量
一条边可以减少一个联通分量
最后是全部成为一个联通分量,所以答案就是联通分量个数-1
维护联通分量用并查集
很秀,关键是想到抽象成一个图
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 1e5 + 10;
int f[MAXN << 1], n, m, q;
int find(int x)
{
if(f[x] == x) return x;
return f[x] = find(f[x]);
}
int main()
{
scanf("%d%d%d", &n, &m, &q);
_for(i, 1, n + m) f[i] = i;
while(q--)
{
int x, y;
scanf("%d%d", &x, &y);
f[find(x)] = find(y + n);
}
int cnt = 0;
_for(i, 1, n + m)
cnt += f[i] == i;
printf("%d\n", cnt - 1);
return 0;
}
校赛题目补完了!!!!
STL
遍历容器时可以用for-range循环
for(auto v: g[u])
比如很常用的vector就可以这样遍历,省去了很多代码
auto是自动匹配类型
这只是取出这个值,要改变要加&
for(auto& v: g[u])
像其他的set,map也一样
还有set,multiset,map,multimap,unordered_set, unordered_map容器
可以insert,erase,count 前三个是logn,后两个是O(1)
unordered_map可以用来给string分配id
set+unordered_map用来离散化