Day4
Day4
T1
纯水题,不知道为啥还得写题解
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define gt getchar
using namespace std;
inline ll read(){
ll x=0,f=1;char ch=gt();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=gt();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=gt();}
return x*f;}
const int N = 600005;
struct edge
{
int to, nxt;
}ed[N];
int head[N], cnt, fa[N];
void add(int u, int v)
{
ed[++ cnt] = (edge){v, head[u]};head[u] = cnt;
ed[++ cnt] = (edge){u, head[v]};head[v] = cnt;
}
int ans = 0;int bj[N], s[N], vis[N];
void dfs(int u)
{
s[u] = 1;
vis[u] = 1;
for(int i = head[u]; i; i = ed[i].nxt)
{
int to = ed[i].to;
if(vis[to] == 1)continue;
vis[to] = 1;
dfs(to);
s[u] += s[to];
}
}
int main()
{
//freopen("ex_authority4.in", "r", stdin);
int n = read();
for(int i = 2; i <= n; ++ i)
{
int v = read();
add(v, i);
fa[i] = v;
}
dfs(1);
for(int i = 1; i <= n; ++ i)if(2 * s[i] - 1 > n)++ ans;
cout << ans;
return 0;
}
T2
这题直接转化为序列问题
对于每个点集
将其中出现的点按大小排序
然后在序列上将在点集中出现的点之间的数提出
设有一个序列
\(9, 8, 7, 6, 5, 4, 3, 2, 1\)
其中 \(7, 4\) 在点集中
则提出 \(7, 6, 5, 4\)
发现其能贡献的方案数为 \(1+4+5+4\times5=5 \times6\)
所以我们将总方案数除以总排列数即为概率,发现答案即为点集中出现的数的乘积
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define gt getchar
using namespace std;
inline ll read(){
ll x=0,f=1;char ch=gt();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=gt();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=gt();}
return x*f;}
const ll p = 998244353;
ll ksm(ll a, ll b)
{
ll ans = 1;
while (b)
{
if (b & 1)
ans = ans * a % p;
a = a * a % p;
b >>= 1;
}
return ans % p;
}
int f[300005];
int main()
{
int n = read(), q = read();
f[0] = 1;
//for(int i = 1; i <= 300000; ++ i)f[i] = f[i - 1] * i % p, f[i] = f[i] % p;
while(q--)
{
int m = read();
ll ans = 1;
int bj = 0, mx = -1;
for(int i = 1; i <= m; ++ i)
{
int v = read();
if(v == 1)bj = 1;
mx = max(mx, v);
ans *= v;ans = ans % p;
}
//cout << "\n";
if(bj == 1){
cout << 1 << '\n';
continue;
}
//cout << "ans: " << ans << "\n";
cout << ksm(ans, p - 2) * mx << '\n';
}
return 0;
}
T3
当且仅当每个连通块边数都是偶数时有解
树可以叶子开始构造
仙人掌可以建圆方树和树类似的构造
海胆图找到中心构造
对于一般图建立 dfs 树 这样非树边只有返祖边
从深到浅考察每个点,考虑向儿子的连边及其连出的非树边中未匹配边数量
若为偶数则两两匹配,否则加入这个点向父亲的连边
除了根节点之外每个点都能用与父亲的连边调整奇偶性
因为连通块一共有偶数条边,所以能完美匹配
//O(n + m)
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define gt getchar
using namespace std;
inline ll read(){
ll x=0,f=1;char ch=gt();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=gt();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=gt();}
return x*f;}
const int N = 300005;
int head[N] , cnt;
int n, m, ecnt;
bool vis[N], usd[N];
struct edge{
int to, nxt, w;
}ed[N << 1];
void add(int u, int v, int w){
ed[++ cnt]={v, head[u], w},head[u] = cnt;
ed[++ cnt]={u, head[v], w},head[v] = cnt;
}
void dfs(int u){
vis[u] = 1;
for(int i = head[u]; i ; i = ed[i].nxt)
{
if(!usd[ed[i].w]) usd[ed[i].w] = 1;
++ ecnt;
}
for(int i = head[u]; i ; i = ed[i].nxt)
{
int v = ed[i].to;
if(!vis[v]) dfs(v);
}
}
int main(){
n = read(), m = read();
for(int i = 1; i <= m; ++ i)add(read(), read(), i);
for(int i = 1; i <= n; ++ i)
{
if(!vis[i]){
ecnt = 0, dfs(i);
if(ecnt & 1)
{
cout << "NO";
return 0;
}
}
}
cout << "YES";
}
T4
记 \(R_i = \min∈[l_j ,r_j ]{r_j}\) ,那么 \(i\) 可以贡献当且仅当 \([i, Ri ]\) 中存在一个点被标记。
暴力 dp 可以做到 \(O(n^2k)\)
注意到如果 \(j ∈ [i, R_i]\),那么 \(R_j ≤ R_i\),所以 \([i, R_i]\) 这些区间之间要么无交,要么互相包含。
据区间包含关系建树,那么我们每次标记可以保证一条到根的链不被破坏。
在树上 dp 的话,使用树上背包的技巧可以做到 \(O(nk)\)。
但是并不需要这么麻烦,我们考虑将树长剖,那么其实我们的答案就是前 \(k\) 条长链的带权长度和。
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define gt getchar
using namespace std;
inline ll read(){
ll x=0,f=1;char ch=gt();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=gt();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=gt();}
return x*f;}
const int N = 300005;
int n, m, k;
int a[N], L[N], R[N], st[N], t = 0, cnt = 0;
ll ans;
ll len[N], c[N];
vector<int> E[N];
void dfs(int u)
{
int son = 0;
for(int v : E[u])
{
dfs(v);
if(len[v] > len[son]) son = v;
}
len[u] = len[son] + a[u];
for(int v : E[u]) if(son ^ v) c[++ cnt] = len[v];
}
int main(){
n = read(), m = read(), k = read();
for(int i = 1; i <= n; ++ i)
{
a[i] = read();
L[i] = i + 1;
ans += a[i];
}
for(int i = 1; i <= m; ++ i)
{
int l = read(), r =read();
L[r] = min(L[r], l);
}
for(int i = n; i >= 1; -- i){
while(t && L[i] <= L[st[t]])
-- t;
if(L[i] <= i) st[++ t] = i;
if(t && L[st[t]] <= i) R[i] = st[t];
else ans -= a[i], R[i] = i - 1;
}
st[t=1] = 0, R[0] = n;
for(int i = 1; i <= n; ++ i){
while(t && R[st[t]] < i) ++ t;
if(R[i] >= i) E[st[t]].push_back(i), st[++ t] = i;
}
dfs(0), c[++ cnt] = len[0];
if(cnt >= k) nth_element(c, c + cnt - k, c + cnt);
for(int i = max(cnt - k, 0); i < cnt; ++ i) ans -= c[i];
cout << ans;
}
听课(
随机树据
就和T2有点像
第 \(i\) 个点随机向 $1 $ 到 \(i - 1\) 连边
树高期望 \(\log_{}n\)
当树从所有有标号无根树中随机选取,那么树的高度期望 \(\sqrt{n}\)
树上倍增
给定一棵树,初始时只有根一个点,你需要维护如下操作:
给定 \(u\),假如现在树上有 \(n\) 个节点,那么新增一个父亲为 \(u\),编号为 \(n + 1\) 的叶子节点。
给定 \(u, v\),询问这两个的 \(lca\)。
强制在线,操作共 \(n\) 次。
\(1 ≤ n ≤ 3e5\)
倍增求 lca,加入节点 \(i\) 时,所有 \(i\) 的祖先的倍增数组已经求出
我们可以利用求出 \(i\) 的倍增数组
所以加入一个叶子的复杂度就是 \(O(log n)\)。
总复杂度 \(O(n log n)\)。
树上差分
在链上建差分 \(O(1)\) 修改 ,\(O(n)\) 还原
长链剖分
寄了(
树的直径和重心
直径:找根,然后找最深和次深(非严格)叶子深度求和
题等补

浙公网安备 33010602011771号