//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

2023.7.31 DAY8 解题报告

2023.7.31 DAY8 解题报告

T1

考试想了一个做法,但是 80 分,考完试想起来有个地方处理的不对,处理完就 A 了。

我们观察一下发现,如果一条链没到头肯定是不优的,因为异或最小就是 0,我们加上也没影响,所以我们可以直接按链上所有都选来算。

我们考虑维护两个值,以当前点的子节点往下的链最大的值以及次大值。

我们这个时候发现,一个点的答案就是最大值与次大值之和。

我们在搜完之后直接 \(O(n)\) 遍历一遍就好。

挂了 20.

#include <bits/stdc++.h>

#define int long long
#define N 1000100

using namespace std;
//0是最大,1是次大
int n, m, a[N], maxn[N][2], f[N], cnt, head[N], ans;
struct sb{int u, v, next;}e[N];

inline void add(int u, int v)
{
    e[++ cnt] = (sb){u, v, head[u]};
    head[u] = cnt;
    return ;
}

inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}

inline void dfs(int x)
{
    for(int i = head[x]; i; i = e[i].next)
    {
        int v = e[i].v;
        dfs(v);
        int val = a[v] ^ a[x];
        if(maxn[x][0] < maxn[v][0] + val)
        {
            maxn[x][1] = max(maxn[x][0], maxn[x][1]);//少这句
            maxn[x][0] = maxn[v][0] + val;
        }
        else if(maxn[x][1] < maxn[v][0] + val) maxn[x][1] = maxn[v][0] + val;
        else if(maxn[x][1] < maxn[v][1] + val) maxn[x][1] = maxn[v][1] + val;
        else continue;
    }
    return ;
}

signed main()
{
//    freopen("a_1.in", "r", stdin);
    n = read();
    for(int i = 1; i <= n; i ++)
        a[i] = read();
    for(int i = 2; i <= n; i ++)
        f[i] = read(), add(f[i], i);
    dfs(1);
    for(int i = 1; i <= n; i ++)
        ans = max(ans, maxn[i][0] + maxn[i][1]);
    cout << ans << endl;
    return 0;
}

T2

我们考虑一下如何搜索,既然都说了是 DAG,那么我直接每一个点都DFS一次,把到的点加一,如果是奇数就直接答案加1,偶数就答案减一。

20分。

#include <bits/stdc++.h>

#define umap unordered_map
#define int long long
#define N 1000100

using namespace std;

const int base = 50021;
int head[N], cnt, n, m, ans;
struct sb{int u, v, next;}e[N << 1];
umap<int, int> mp;

inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}

inline void add(int u, int v)
{
    e[++ cnt] = (sb){u, v, head[u]};
    head[u] = cnt;
    return ;
}

inline void dfs(int x, int tp)
{
    for(int i = head[x]; i; i = e[i].next)
    {
        int v = e[i].v;
        int tmp = tp * base + v;
        mp[tmp] ++;
        if(mp[tmp] & 1) ans ++;
        else ans --;
        dfs(v, tp);
    }
    return ;
}

signed main()
{
    //    freopen("b_1.in", "r", stdin);
    n = read(), m = read();
    for(int i = 1; i <= m; i ++)
        add(read(), read());
    for(int i = 1; i <= n; i ++)
        dfs(i, i);
    cout << ans << endl;
    return 0;
}

我们发现二维数组难以存放我们的路径数,但是答案只与奇偶性有关,所以可以用 bitset 和异或来存储。

但是时间不够,我们发现可以利用之前存的一个点的情况来更新能到他的点的情况。

#include <bits/stdc++.h>

//#define umap unordered_map
#define P 1000000007
#define int long long
#define N 100010

using namespace std;

//umap<int, int>;
int n, m, fa[N], f[N][110], b[N], ans, ff, head[N], cnt, vis[N], V[N];
struct sb{int u, v, next;}e[N];

inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}

inline int stling(int p, int k)
{
    f[0][1] = 1;
    for(int i = 1; i <= p; i ++)
        for(int j = 1; j <= min(i, k); j ++)
            f[i][j] = (f[i - 1][j] * j + f[i - 1][j - 1]) % P;
    return f[p][k];
}

inline void add(int u, int v)
{
    e[++ cnt] = (sb){u, v, head[u]};
    head[u] = cnt;
    return ;
}

inline int pd()
{
    for(int i = 0; i < m; i ++) V[i] = 0;
    for(int i = 1; i <= n; i ++) V[b[i]] = 1;
    for(int i = 0; i < m; i ++) if(V[i] == 0) return 0;
    queue<int> q;
    for(int i = 1; i <= n; i ++) vis[i] = 0;
    int s = 1;
    q.push(s);
    while(!q.empty())
    {
        int u = q.front(); q.pop();
        if(vis[u]) continue;
        vis[u] = 1;
        int ch = 0;
        for(int i = head[u]; i; i = e[i].next)
        {
            int v = e[i].v;
            ch ++;
            if(!vis[v]) q.push(v);
        }
        if(ch == 0)
        {
//            cout << u << endl;
            for(int i = 0; i < m; i ++) V[i] = 0;
            int yy = u;
            while(yy)
            {
                if(V[b[yy]]) return 0;
                V[b[yy]] = 1;
                yy = fa[yy];
            }
        }
    }
//    for(int i = 1; i <= n; i ++)
//        cout << b[i] << " ";
//    cout << endl;
    return 1;
}

inline void dfs(int x)
{
    if(x == n + 1){if(pd()) ans ++;return ;}
    for(int i = 1; i < m; i ++)
        b[x] = i, dfs(x + 1);
    return ;
}

signed main()
{
    n = read(), m = read();
    int fm = 1;
    for(int i = 2; i < m; i ++) fm *= i;
    for(int i = 2; i <= n; i ++)
    {
        fa[i] = read();
        if(fa[i] != 1) ff = 1;
        add(fa[i], i);
    }
    if(ff == 0) cout << stling(n - 1, m - 1) << endl;
    else {dfs(2); cout << ans / fm << endl;}
    return 0;
}

T3

二分答案 \(mid\),检查是否存在合法方案使得根节点答案大于等于 \(mid\)

\(f[x]\) 表示最少在 \(x\) 的子树内分配多少个大于等于 \(mid\) 的叶子权值,使得 \(x\) 的权值大于等于 \(mid\)

那么就是选择 \(\frac{k}{2}\) 个儿子节点,那么 \(f[x]=\sum f[y]\)。所以将儿子按照 \(f[x]\) 排序,从小到大选择,最终判断 \(f[x]\) 是否小于等于可分配的叶子权值个数。

#include<bits/stdc++.h>

#define int long long
#define Inf 1000010
#define N 200010

using namespace std;

inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}

int n, k, fa[N], leafv[N], mid, res, sz[N], f[N];
vector<int> e[N];

void dfs(int u)
{
    if(u > n - k)
    {
        if(leafv[u]) (f[u] = leafv[u] < mid? Inf : 0), sz[u] = 0;
        else f[u] = sz[u] = 1;
        return ;
    }
    sz[u] = 0;
    vector<int> vec;
    for(int v : e[u])
    {
        dfs(v); sz[u]+=sz[v];
        vec.push_back(f[v]);
    }
    sort(vec.begin(), vec.end());
    int s = 0;
    for(int i = 0; i <= vec.size() / 2; i ++) s += vec[i];
    f[u] = (s > sz[u] ? Inf : s);
}

inline int check()
{
    int Res = k - mid + 1;
    for(int i = n - k + 1; i <= n; i ++) Res -= (leafv[i] >= mid);
    dfs(1);
    return (f[1] <= Res);
}

signed main()
{
    n = read(), k = read();
    for(int i = 2; i <= n; i ++) fa[i] = read(), e[fa[i]].push_back(i);
    for(int i = n - k + 1; i <= n; i ++) leafv[i] = read();
    int L = 0, R = n + 1;
    while(L + 1 < R)
    {
        mid = (L + R) / 2;
        if(check()) L = mid;
        else R = mid;
    }
    cout << L << endl;
    return 0;
}

T4

看到有 25 的暴力分,直接暴力。

1 直接自己一个集合,也就是枚举 \(n-1\) 个点分到 \(m-1\) 个点里,这样复杂度降低不少。我们枚举的不能重复,所以枚举完最后要除以 \((m-1)!\)

后面有 30 分特殊性质,直接第二类斯特林数 \(O(nm)\) 递推过去就行。

#include <bits/stdc++.h>

//#define umap unordered_map
#define P 1000000007
#define int long long
#define N 100010

using namespace std;

//umap<int, int>;
int n, m, fa[N], f[N][110], b[N], ans, ff, head[N], cnt, vis[N], V[N];
struct sb{int u, v, next;}e[N];

inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}

inline int stling(int p, int k)
{
    f[0][1] = 1;
    for(int i = 1; i <= p; i ++)
        for(int j = 1; j <= min(i, k); j ++)
            f[i][j] = (f[i - 1][j] * j + f[i - 1][j - 1]) % P;
    return f[p][k];
}

inline void add(int u, int v)
{
    e[++ cnt] = (sb){u, v, head[u]};
    head[u] = cnt;
    return ;
}

inline int pd()
{
    for(int i = 0; i < m; i ++) V[i] = 0;
    for(int i = 1; i <= n; i ++) V[b[i]] = 1;
    for(int i = 0; i < m; i ++) if(V[i] == 0) return 0;
    queue<int> q;
    for(int i = 1; i <= n; i ++) vis[i] = 0;
    int s = 1;
    q.push(s);
    while(!q.empty())
    {
        int u = q.front(); q.pop();
        if(vis[u]) continue;
        vis[u] = 1;
        int ch = 0;
        for(int i = head[u]; i; i = e[i].next)
        {
            int v = e[i].v;
            ch ++;
            if(!vis[v]) q.push(v);
        }
        if(ch == 0)
        {
//            cout << u << endl;
            for(int i = 0; i < m; i ++) V[i] = 0;
            int yy = u;
            while(yy)
            {
                if(V[b[yy]]) return 0;
                V[b[yy]] = 1;
                yy = fa[yy];
            }
        }
    }
//    for(int i = 1; i <= n; i ++)
//        cout << b[i] << " ";
//    cout << endl;
    return 1;
}

inline void dfs(int x)
{
    if(x == n + 1){if(pd()) ans ++;return ;}
    for(int i = 1; i < m; i ++)
        b[x] = i, dfs(x + 1);
    return ;
}

signed main()
{
    n = read(), m = read();
    int fm = 1;
    for(int i = 2; i < m; i ++) fm *= i;
    for(int i = 2; i <= n; i ++)
    {
        fa[i] = read();
        if(fa[i] != 1) ff = 1;
        add(fa[i], i);
    }
    if(ff == 0) cout << stling(n - 1, m - 1) << endl;
    else {dfs(2); cout << ans / fm << endl;}
    return 0;
}

考虑从上到下分配集合,或者按 DFS 序分配,保证祖先在自己之前分配集合即可。那么有 \(f[x][y] = f[x - 1][y] \times (y - dep) +f[x - 1][y - 1]\),当 \(y \ge dep\) 时。

这代表放到之前的集合内(不能和祖先放在一起,其他的集合任意放),或者自己新建

一个集合。

#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
#define int long long
using namespace std;

const int P = 1e9 + 7;
const int N = 100500, M = 105;
int h[N], ne[N], to[N], tot, cnt, m, n;
inline void add(int x, int y) {
	ne[++tot] = h[x], to[h[x] = tot] = y;
}

int dp[N][M];

void dfs(int x, int dep) {
	++cnt;
	for (int i = dep; i <= m; ++i)
		dp[cnt][i] = (dp[cnt - 1][i] * (i - dep + 1) + dp[cnt - 1][i - 1]) % P;
	for (int i = h[x]; i; i = ne[i]) dfs(to[i], dep + 1);
}

signed main() {
	cin>>n>>m;
	dp[0][0] = 1;
	for (int i = 2, f; i <= n; ++i) cin>>f,add(f, i);
	dfs(1, 1);
	cout<<dp[n][m]<<endl;
	return 0;
}

.

posted @ 2023-07-31 22:30  北烛青澜  阅读(11)  评论(0)    收藏  举报