南外集训Day6
由于我太菜了所以我很菜……、
还好集训分两次,中间给了我时间来补这些博客。
不过话说这些南外的老师似乎特别喜欢搞
我太菜了!
通过的题
提一下吧,第一题因为题目没看清交了 puts
,然后本地过了样例,在线测评一直错,然后我懵了整整
会但是没通过的题
不会的题
F
原题。
首先审清题意:这是完全图!!!
赛时想到的超级接近正解,但是因为当时觉得很麻烦就没有打了,但实际上再理一理思路之后会发现非常简单。
赛时首先就想到了一个状态,
正如上所述,事实正解和这个的方法基本一样,但只是把我两维的顺序
给出简单的DP式:
其中那个组合数的意义是从
考虑到
最后赞一下,这道题很好地又考察了我们的
#include <bits/stdc++.h>
#define ll long long
#define L(i, a, b) for(int i = a; i <= b; i++)
#define R(i, a, b) for(int i = a; i >= b; i--)
using namespace std;
const int N = 251;
const ll mod = 998244353;
int n, k, c[N][N], p[N][N * N], dp[N][N];
int main(){
scanf("%d%d", &n, &k);
c[0][0] = 1;
L(i, 1, n){
L(j, 0, n){
if(!j) c[i][j] = c[i - 1][j];
else c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mod;
}
}
L(i, 1, k){
p[i][0] = 1;
L(j, 1, n * n)
p[i][j] = 1ll * p[i][j - 1] * i % mod;
}
dp[0][0] = 1;
L(i, 1, k){
L(j, 0, n - 1){
dp[i][j] = dp[i - 1][j];
L(t, 0, j - 1)
dp[i][j] = (dp[i][j] + 1ll * dp[i - 1][t] * c[n - 1 - t][j - t] % mod * p[k - i + 1][t * (j - t) + (j - t) * (j - t - 1) / 2] % mod) % mod;
}
}
printf("%d\n", dp[k][n - 1]);
return 0;
}
G
原题。
这道题的套路是两种经典套路相结合。
- 对于一类二分题,我们可以在二分后把满足条件的设为
,不满足的设为 或 (视情况而定),这时我们能很方便与统计/做前后缀和。 - 对于一类动态的前后缀和问题(或者上述问题),很多时候可以用线段树维护动态的。
我太菜了,第一种套路都没想到。
所以到底该怎么做呢?我们先从简单入手。假设我们已经确定了哪些位置有炸弹,那么可以套用第一种套路。就是去二分答案
由于这种做法非常的逊,所以我们考虑优化他(对每个数做一遍的时间复杂度为
由于有单调性所以能二分,而我们发现答案是单调递减的,所以呢我们发现在这样的性质下一个答案是合法的仅当有一个点右边的大于等于
#include <bits/stdc++.h>
#define L(i, a, b) for(int i = a; i <= b; i++)
#define R(i, a, b) for(int i = a; i >= b; i--)
using namespace std;
const int N = 300010;
struct Tree{
int l, r, mid, mx, lazy;
void Tag(int v){mx += v, lazy += v;}
}t[N << 2];
int n, id[N], p[N], q[N];
void Pushup(int p){
t[p].mx = max(t[p << 1].mx, t[p << 1 | 1].mx);
}
void Pushdown(int p){
t[p << 1].Tag(t[p].lazy);
t[p << 1 | 1].Tag(t[p].lazy);
t[p].lazy = 0;
}
void Build(int p, int l, int r){
t[p] = {l, r, l + r >> 1, 0, 0};
if(l == r) return;
Build(p << 1, t[p].l, t[p].mid);
Build(p << 1 | 1, t[p].mid + 1, t[p].r);
Pushup(p);
}
void Update(int p, int l, int r, int v){
if(l <= t[p].l && t[p].r <= r){
t[p].Tag(v);
return;
}
Pushdown(p);
if(l <= t[p].mid) Update(p << 1, l, r, v);
if(t[p].mid < r) Update(p << 1 | 1, l, r, v);
Pushup(p);
}
int main(){
scanf("%d", &n);
L(i, 1, n) scanf("%d", &p[i]), id[p[i]] = i;
L(i, 1, n) scanf("%d", &q[i]);
Build(1, 1, n);
int x = n;
L(i, 1, n){
if(i > 1) Update(1, 1, q[i - 1], -1);
while(t[1].mx <= 0) Update(1, 1, id[x--], 1);
printf("%d ", x + 1);
}
return 0;
}
H
原题。
这道题是个简单题,但是由于我太菜了所以没做出来。
根据数据范围,发现是个
我们再一思考,这道题是在树上,树上
- 其一,最简单的,一维状态,由子节点或者父节点转移过去。但是显然你想不太到这么简单的方案;
- 二维的状态,那第二维该弄个什么好呢?发现好像只能是块的个数。但是发现这样就变成树上背包了,也就是下面那种。
-
那就是树上背包。
我们来具体讲一下怎么树上背包。首先状态不说,肯定是
表示以 这个节点为根的子树分成 块,不算 所处的那一块的最大值,然后我们称一个点的权值为 ,其等于 。然庵后我们经思考后,发现好像不能直接去树上背包,原因是你还是不知道当前根节点 这一块的权值是多少。当时我就愣住了,之后便认为这道题恐怖如斯,不是我能做出来的题。但这时候就应该去找一些性质或者贪心策略以使这个问题可以迎刃而解。所以我们找到了一个非常漂亮的性质(贪心策略):我们思考两种方案,第一种的块数比第二种多,但是第二种
那个联通块的权值和大于第一种。
为了防止搞混两种,再清晰地理一下:
- 块数多
那个联通块的权值大
第一种一定 不劣于 第二种!
我们采取反证法,思考假设第二种比第一种优会优再什么情况下:可能存在第二种他的权值很大,然后把上边权值是非正数的一块变成正数了,然后会使答案加
那么假设有两种方案块数相等,不难想到u那块必定是权值越大越好,因为可能越大。那么我们再用一个
最后给转移方程(这里用的是刷表法):
-
所处的联通块不和 合并,即单独划分用
去更新 的最优值。 -
的那个联通块与 合并用
去更新 的最优值。
边界是
树上背包的时间复杂度
其实这跑得过去是必然的,因为我们考虑最坏情况,
#include <bits/stdc++.h>
#define ll long long
#define L(i, a, b) for(int i = a; i <= b; i++)
#define R(i, a, b) for(int i = a; i >= b; i--)
using namespace std;
const int N = 3010;
int T, n, m, a[N], b[N], w[N], sz[N];
int f[N][N], r[N];
ll g[N][N], c[N];
vector<int>e[N];
void Init(){
memset(f, 0, sizeof(f));
memset(g, 0xf3, sizeof(g));
L(i, 1, n) e[i].clear();
}
void Dfs(int u, int pa){
f[u][1] = 0, g[u][1] = a[u];
sz[u] = 1;
for(int v: e[u]){
if(v == pa) continue;
Dfs(v, u);
L(i, 1, min(m, sz[u] + sz[v]))
r[i] = 0, c[i] = -3e12;
L(i, 1, min(m, sz[u])){
L(j, 1, min(m, sz[v])){
ll ff = f[u][i] + f[v][j] + (g[v][j] > 0), gg = g[u][i];
if(i + j <= m && ff > r[i + j] || ff == r[i + j] && gg > c[i + j])
r[i + j] = ff, c[i + j] = gg;
ff = f[u][i] + f[v][j], gg = g[u][i] + g[v][j];
if(i + j - 1 <= m && ff > r[i + j - 1] || ff == r[i + j - 1] && gg > c[i + j - 1])
r[i + j - 1] = ff, c[i + j - 1] = gg;
}
}
L(i, 1, min(m, sz[u] + sz[v]))
f[u][i] = r[i], g[u][i] = c[i];
sz[u] += sz[v];
}
}
void Solve(){
scanf("%d%d", &n, &m); Init();
L(i, 1, n) scanf("%d", &b[i]);
L(i, 1, n) scanf("%d", &w[i]);
L(i, 1, n) a[i] = w[i] - b[i];
int u, v;
L(i, 1, n - 1){
scanf("%d%d", &u, &v);
e[u].push_back(v), e[v].push_back(u);
}
Dfs(1, 0);
printf("%d\n", f[1][m] + (g[1][m] > 0));
}
int main(){
scanf("%d", &T);
while(T--) Solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现