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)\) 还原

长链剖分

寄了(

树的直径和重心

直径:找根,然后找最深和次深(非严格)叶子深度求和

题等补

posted @ 2023-07-27 20:36  Qinzh  阅读(17)  评论(1)    收藏  举报