最近几天一直忙着复习考试,把前面落下的其他课程知识补起来,整理每天的笔记,都没怎么写题了
其实也有自己的原因,平时训练的时候我都是拿到题目没思路直接看答案,而不是去想可能是怎么做得,我要怎么实现?
所以一直都是无效的,也难怪没什么动力,因为这种方式根本没有积累,
只有经过大脑预处理之后,才有被真正理解的可能性,才有可能积累,才有可能取得微小的进步,最后汇集为不可阻挡的大进步
印证了那句正确的话,正确的态度就应该是,把练习当考试,把考试当练习
昨天只过了一题,嗯,所以我来补题学知识了
还有,真正重要的东西往往只有重复很多次之后,才有被真正消化的可能性
过题
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;
}
思路:
乍一看,最长递增子序列?
然后模拟一下,觉得可能是扫描线之类的,对于每个x都有y与之对应
然后就不会了...
题解思路:
样表,对勾...
嗯,我完全想错了,而且这个题好像不好理解,并且用了没见过的科技树
暂时放过
1004
题意:
给01串,找到长度为k的全为0或者全为1的下标开始位置,不存在返回-1,然后翻转,多次询问有影响
思路:
(我对于线段树的理解仅限于树状数组的高级玩法 + 可以实现区间修改,只写过板子题,至于维护信息没有写过...还是练的少了,关于我找了很多博客只找到基础应用解释的时候,我人傻了,难道维护信息真的没有吗)
很明显是区间修改,直接想到线段树
线段树维护信息:首字符开头的线段长度
然后二分找第一次长度为k的位置
然后区间修改
细节问题:
Q:如何维护连续1/0的个数?
A:强大的lazy标记
到这里我已经知道我补不动了,因为线段树的lazy维护信息我根本一窍不通,所以都不会写,更看不懂
其他的题目通过题解大致扫了一下,发现,剩下的算法我是一个不会啊,还有一个贪心AK题...不是我能碰的
整理一下这一场发现的漏洞:
1.杨氏矩阵
2.母函数多项式
3.线段树lazy维护信息使用
4.网络流
5.双连通分量
(我多久没有学习新算法了...)
这是下次比赛之前的工程列表了,希望可以补完吧,我有时间都会写博客整理的,没人看就不管了

浙公网安备 33010602011771号