yuwj  

最近几天一直忙着复习考试,把前面落下的其他课程知识补起来,整理每天的笔记,都没怎么写题了
其实也有自己的原因,平时训练的时候我都是拿到题目没思路直接看答案,而不是去想可能是怎么做得,我要怎么实现?
所以一直都是无效的,也难怪没什么动力,因为这种方式根本没有积累,
只有经过大脑预处理之后,才有被真正理解的可能性,才有可能积累,才有可能取得微小的进步,最后汇集为不可阻挡的大进步
印证了那句正确的话,正确的态度就应该是,把练习当考试,把考试当练习
昨天只过了一题,嗯,所以我来补题学知识了

还有,真正重要的东西往往只有重复很多次之后,才有被真正消化的可能性

过题

1008

简单的排列组合问题,讨论一下,选择一下
ans = abcde + abc*C(d, 2)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = (a); i <= b; i++)
#define dwn(i, a, b) for(int i = (a); i >= b; i--)
using ll = long long;
const int N = 105, mod = 1e9 + 7;

ll qmi(ll a, int k){
    ll res = 1;
    while(k){
        if(k&1) res = res * a % mod;
        a = a * a % mod;
        k >>= 1;
    }
    return res;
}

ll fac[N], ifac[N];
void init(){
    fac[0] = 1;
    rep(i, 1, N){
        fac[i] = fac[i-1] * i % mod;
    }

    //cout << fac[50] << '\n';

    ifac[N] = qmi(fac[N], mod - 2);
    dwn(i,N,1){
        ifac[i-1] = ifac[i] * i % mod;
    }
}

ll C(int n, int m){
    return fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}

void solve(){
    int a,b,c,d,e;
    cin >> a >> b >> c >> d >> e;
    long long ans = a * b * c * d * e;
    if(d>=1) ans += a* b * c * C(d, 2) % mod;
    cout << ans << '\n';
}

int main(){
    ios::sync_with_stdio(0);cin.tie(0);
    int t;
    cin >> t;
    init();
    while(t--){
        solve();
    }
    return 0;
}

补题

1007

题意:
树上取2个点s, t,问从s点到达t点最大的路径权值和,有负边

思路:
注意点:
1.不是最短路/最小花费 -> 不是一条路径, 而是可以走很多路!能将路径上的所有权值都收集到!
2.树上两个结点之间只有一条路径
简化提炼:
1.树上边权最大和,所有边权都可达
2.两节点之间只有一条路径

如图:

因此,
定义:dp[u]:以u为根节点的最大子树和
转移:
子树v不包含节点t:dp[u] = sum(dp[v] + p + q, 0)
// 如果是负数,直接不要,否则,就能对答案产生正贡献,这颗子树必要,而且不含有节点t,必须返回根节点
子树v包含节点t:dp[u] = dp[v] + p,必须走下去,没商量,而且只有一条路

于是,整理流程就是:
dfs遍历u,在遍历过程中,判断子树是否含有t节点,然后累加答案,最后输出ans

细节处理:
1.两遍dfs,第一遍处理最大子树和,第二遍统计答案
2.如何判断是不是节点t?dfs利用信息返回判断到没到

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N  = 1e5 + 10;
ll dp[N];
int s, t;
vector<tuple<int,int,int>> g[N];

void dfs(int u, int fa){
    dp[u] = 0;
    for(auto [v, p, q] : g[u]){
        if(v == fa) continue;
        dfs(v, u);
        dp[u] += max(0LL, dp[v] + p + q);
    }
}

pair<bool, ll> dfs2(int u, int fa){
    if(u == t){
        return {true, dp[u]};
    }

    bool flg = false;
    ll res = 0;
    for(auto [v, p, q] : g[u]){
        if(v == fa) continue;
        auto [ff, tmp] = dfs2(v,u);
        if(ff){
            flg = true, res += tmp + p; // 含有t的子树也是有其他的同层级的子树可以有边权全部加起来的
        }else{
            res += max(dp[v] + p + q, 0LL); // 没有就是直接加入了
        }
    }

    return {flg, res};
}

void solve(){
    int n; cin >> n;
    for(int i = 0; i <= n; i++) g[i].clear();
    for(int i = 0; i < n-1; i++){
        int u,v,p,q; cin >> u >> v >> p >> q;
        g[u].push_back({v,p,q});
        g[v].push_back({u,q,p}); // 细节存储有向边权
    }
  
    cin >> s >> t;
    dfs(s,0);

    auto [flg, ans] = dfs2(s, 0);
    cout << ans << '\n';
}

int main(){
    ios::sync_with_stdio(0);cin.tie(0);
    int T;
    cin >> T;
    while(T--){
        solve();
    }
    return 0;
}

1009

题意:n个节点,m个项目,每个节点只能匹配一个项目,每个项目价值w,问最大价值

思路:
在看题解之前,先回忆一下比赛时坐牢的思路:
还是dp,图上每两个节点之间有一些边,边权不同,这两个节点贪心选择最大的两条边就好了

正确思路:
项目为边建图
难点:如何表示每个节点只能选择一个项目?
前置思考:连通块中有多少个节点最多能选择多少条边
按照价值排序边,每次选择边都合并节点,考察是否边数 <= 点数,
如果边数 < 点数,则可以加入边
经过调试发现,如果两个节点之间有超过两条“最”大价值边,则边数 > 点数,无法全部选择,就实现了选择最大的两条边的思路
因为已经排序过,且不会选择多余,所以答案是正确的

连通块的权值和:
连通块每个节点只能选择一条边 -> 并查集记录边数和点数满足限制约束

点击查看代码
#include <bits/stdc++.h>
using namespace std;

struct Node{
    int u, v, w;
};

struct DSU{
    vector<int> fa, edge, node;

    DSU(int n){
        fa.resize(n+1);
        edge.assign(n+1, 0);
        node.assign(n+1, 1);
        iota(fa.begin(), fa.end(), 0);
    }

    int find(int u){
        if(u == fa[u]) return u;
        return fa[u] = find(fa[u]);
    }

    void merge(int x, int y){
        int rx = find(x), ry = find(y);
        if(rx != ry){
            if(node[rx] < node[ry]) swap(rx,ry);
            fa[ry] = rx;
            
            node[rx] += node[ry];
            edge[rx] += edge[ry];
        }
    }
};

void solve(){
    int n, m; cin >> n >> m;
    vector<Node> g(m);
    for(int i = 0; i < m; i++){
        cin >> g[i].u >> g[i].v >> g[i].w;
    }

    sort(g.begin(), g.end(), [](const Node &a, const Node &b) { return a.w > b.w; });


    DSU dsu(n);

    long long ans = 0;
    for(auto [u, v, w] : g){
        int ru = dsu.find(u), rv = dsu.find(v);

        if(ru == rv){
            if(dsu.edge[ru] < dsu.node[ru]){
                ans += w;
                dsu.edge[ru]++;
            }
        }else{
            if(dsu.edge[ru] < dsu.node[ru] || dsu.edge[rv] < dsu.node[rv]){
                ans += w;
                dsu.merge(u, v);
                dsu.edge[dsu.find(u)]++;
            }
        }
    }

    cout << ans << '\n';
}

int main(){
    ios::sync_with_stdio(0);cin.tie(0);
    int T;
    cin >> T;
    while(T--){
        solve();
    }
    return 0;
}
赛后题目: # 1010 题意: m * m 网格中,选择n个坐标严格递增的点,且 xi < yi,求方案数

思路:
乍一看,最长递增子序列?
然后模拟一下,觉得可能是扫描线之类的,对于每个x都有y与之对应
然后就不会了...

题解思路:
样表,对勾...
嗯,我完全想错了,而且这个题好像不好理解,并且用了没见过的科技树
暂时放过

1004

题意:
给01串,找到长度为k的全为0或者全为1的下标开始位置,不存在返回-1,然后翻转,多次询问有影响

思路:
(我对于线段树的理解仅限于树状数组的高级玩法 + 可以实现区间修改,只写过板子题,至于维护信息没有写过...还是练的少了,关于我找了很多博客只找到基础应用解释的时候,我人傻了,难道维护信息真的没有吗)
很明显是区间修改,直接想到线段树
线段树维护信息:首字符开头的线段长度
然后二分找第一次长度为k的位置
然后区间修改

细节问题:
Q:如何维护连续1/0的个数?
A:强大的lazy标记

到这里我已经知道我补不动了,因为线段树的lazy维护信息我根本一窍不通,所以都不会写,更看不懂

其他的题目通过题解大致扫了一下,发现,剩下的算法我是一个不会啊,还有一个贪心AK题...不是我能碰的
整理一下这一场发现的漏洞:
1.杨氏矩阵
2.母函数多项式
3.线段树lazy维护信息使用
4.网络流
5.双连通分量
(我多久没有学习新算法了...)
这是下次比赛之前的工程列表了,希望可以补完吧,我有时间都会写博客整理的,没人看就不管了

posted on 2025-04-19 10:59  xiaowang524  阅读(57)  评论(0)    收藏  举报