先看补题进度:

赛时一题没过,第一题狂wa,情绪直接崩溃了,后面就全是没学过的算法,一个简单dp只能看出是dp,其他的一点想法没有,上升子序列直接TLE
再后面就是盯着题目发呆,负面情绪持续累积
昨天,我的第一场vp就这样了,今天补题补了一天,简单题还能理解,但是中等困难题全是没学过的算法,我天天在cf上写题也没见到那么多算法啊,还以为一直都是思维思维思维,基础算法点完就在瘾cf,算法题几乎没见几个,全是基础 + 思考(可能因为我刷的rating1800~2000还没有涉及中高级算法...)
现在知道积累少了,竟然选择走这条路,就好好走下去
嗯,所以,今天虽然补了一天,但是有效时间可能也就4h左右,大部分时间都是对着代码发呆,完全看不懂。然后就是,收集博客查阅资料。
就算也在一直问AI思路是怎样的,但是就是很少有那种一句话就点醒梦中人的感觉,问15遍左右,才会得到恍然大悟的回复
但是,有个新发现,因为一天基本只搞了2题,G和D,G昨天就理解了大部分,今天早上也问到了关键,但是D...一天了还没弄明白
我先去找dsu on tree的博客,发现没什么用,基本都没讲什么
然后就去找课,B站上无果,但是牛客数据结构课讲了dsu on tree,99入手了,然后就恍然大悟了,现在终于知道那个dsu on tree的颜色种类数到底是怎么算的了,本来想写篇博客总结的,但是我也只是会了板子而已,关于区域赛这个数相同LCA点对数量的应用我就无法理解了...也就在这里卡了半天,没有进展,废话就总结到这里,来看正文(竟然全网搜不到一篇补2025北京市区域赛的博客)
C:
枚举
就是题解那个意思
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solve(){
ll n;
int m;
cin >> n >> m;
if (m == 1) {
cout << (n == 1 ? 1 : -1) << "\n";
return;
}
ll ans = LLONG_MAX;
for (int k = 1; k < m; ++k) {
ll S = (1LL << k) - 1;
if (S >= n) {
ans = min(ans, 1LL << (k - 1));
break;
}
ll rem = n - S;
ll slots = m - k;
ll x = (rem + slots - 1) / slots;
ll lo = 1LL << (k - 1);
ll hi = 1LL << k;
if (x >= lo && x <= hi) {
ans = min(ans, x);
}
}
if (ans == LLONG_MAX)
cout << -1 << "\n";
else
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) solve();
return 0;
}
G: DP
约束dp,子序列特定以xxx为结尾的状态,
相邻不同,维护最大和次大,候选3个选2更新
状态转化降为DP,两个限制条件求解最长子序列
这种问题,这里的核心是:
以p为结尾定义状态,不同颜色限制维护最大和次大值
以不同的颜色做转移:
对于每个数分解的约数p,去找能拿到的最长链,将当前第i位加到那条链上
那么现在就有3条不同长度的候选链:加了新元素的最长链、没加之前的以p结尾的最长和次长链,选出最长的两条链拿去更新就好了
求最长子序列,相邻颜色不同 + 相邻亮度不互质
思路:
先考虑不互质:
dp[p]:以p为素因子的最长子序列的长度
转移:
- 对当前亮度 $w_i$ 的所有质因子 p,挑一条尾色 ≠ $c_i$ 的最长链,取最大长度
best; cur = best + 1。
更新:
- 把 这条新链 (cur, cᵢ) 投递到它包含的每个质因子 p 的排行榜里;
- 用上表 A/B/C 规则,保证
best1[p]、best2[p]始终是 颜色互异的前两名。
代码:
/*
质因子分解表:线性筛 + 最小质因子 = 质因子分解表(不断压榨)
状态降维DP(非i为节点遍历,特殊变量):在所有候选答案中,找到最长的,然后去更新,维护最大和次大
eg:不互质,颜色不同...
*/
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define dwn(i,a,b) for(int i = a; i >= b; i--)
#define vi vector<int>
typedef long long ll;
const int N = 5e5+10;
int n, w[N], c[N],spf[N+1], cur, curbest, ans;
//质因子分解表:线性筛 + 最小质因子 = 质因子分解表(不断%/)
void build_spf(){
vector<int> prime;
for(int i = 2; i <= N; i++){
if(!spf[i]){
spf[i] = i;
prime.push_back(i);
}
for(auto p : prime){
if(i*p > N)break;
spf[p*i] = p;
if(i%p == 0)break;
}
}
}
vector<int> factor(int x){
vector<int> fs;
while(x>1){
int p = spf[x];
fs.pb(p);
while(x%p == 0) x/=p;
}
return fs;
}
struct Node{
int len, col;
}best1[N], best2[N];
void solve(){
cin >> n;
rep(i,1,n) cin >> w[i];
rep(i,1,n) cin >> c[i];
for(int i = 1; i <= n; i++){
curbest = 0;
auto fs = factor(w[i]);
for(auto p : fs){
if(c[i] != best1[p].col) curbest = max(curbest, best1[p].len);
else curbest = max(curbest, best2[p].len);
}
cur = curbest + 1;
ans = max(cur, ans);
for(auto p : fs){
if(c[i] == best1[p].col){
best1[p].len = max(cur, best1[p].len);
}else{
if(cur > best1[p].len){
best2[p] = best1[p];
best1[p] = {cur, c[i]};
}else if(cur > best2[p].len){
best2[p] = {cur, c[i]}; //接上去
}
}
}
}
cout << ans << '\n';
}
int main(){
build_spf();
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
//int _;cin>>_;while(_--)
solve();return 0;
}
(这道题目可能是理解最通透的了)
D: dsu on tree
这个题目可以拆分为:dsu on tree + 树状数组统计区间数量
也可能是将树状数组存错子树信息再来小-大合并
这里给个智乃的dsu on tree的博客,进阶咱没明白,基本的颜色种类数咱得会吧
(得懂点树剖的基本)
https://blog.nowcoder.net/n/a84c24c37daf450d8bf9db81607c8f98
计数就是题解那个意思,树状数组那块我现在还不太明白
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5,INF=1e9;
int T,n;
vector<int> e1[N], e2[N];
int dfn[N], timer, lp[N], rp[N], pos[N], f[N][20], dep[N];
void dfs1(int x, int fa){
dep[x]=dep[fa]+1; f[x][0]=fa;
for(int i=1; i<20; ++i) f[x][i]=f[f[x][i-1]][i-1];
dfn[x]=++timer; pos[timer]=x; lp[x]=timer;
for(auto y:e2[x]) if(y^fa) dfs1(y, x);
rp[x]=timer;
}
int rt[N];
int sz[N], son[N];
vector<int> vec[N];
int jump(int x, int y){
for(int i=19; i>=0; --i) if(dep[f[x][i]]>dep[y]) x=f[x][i];
return x;
}
void dfs2(int x, int fa){
sz[x]=1; son[x]=0;
for(auto y:e1[x]) if(y^fa) {
dfs2(y, x);
sz[x]+=sz[y];
if(sz[y]>sz[son[x]]) son[x]=y;
}
}
struct BIT{
int tr[N];
inline void add(int x, int v){
for(; x<=n; x+=(x&-x)) tr[x]+=v;
}
inline int get(int l, int r){
int ret=0;
for(; r; r-=(r&-r)) ret+=tr[r];
for(--l; l; l-=(l&-l)) ret-=tr[l];
return ret;
}
}B;
long long ans=0;
void dfs3(int x, int fa, int flag){
rt[x]=x; vec[x].push_back(x);
for(auto y:e1[x]) if(y!=fa&&y!=son[x]){
dfs3(y, x, 0);
}
if(!son[x]) {
if(flag) B.add(dfn[x], 1);
return ;
}
dfs3(son[x], x, 1); //重儿子保留,让他做未来的"大桶"
ans+=B.get(lp[x], rp[x]); // 统计重儿子的ans,区间L∩subtreeT(x)
rt[x]=rt[son[x]];
vec[rt[x]].push_back(x); // vec[]:当前在树状数组中的节点列表,加入当前点x
B.add(dfn[x], 1); // 树状数组添加节点(BIT里面存的是一段dfn序,方便得到整个子树的L子树大小)
for(auto y:e1[x]) if(y!=fa&&y!=son[x]){
for(auto t:vec[rt[y]]){
if(!(lp[x]<=dfn[t]&&dfn[t]<=rp[x])) continue;
int ft=jump(t, x);
ans+=B.get(lp[x], rp[x]);
ans-=B.get(lp[ft], rp[ft]);
}
for(auto t:vec[rt[y]]){
vec[rt[x]].push_back(t);
B.add(dfn[t], 1);
}
}
if(!flag){
for(auto t:vec[rt[x]]) B.add(dfn[t], -1);
}
}
signed main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1; i<=n; ++i) e1[i].clear(), e2[i].clear();
for(int i=1, x, y; i<n; ++i){
scanf("%d%d", &x, &y);
e1[x].push_back(y); e1[y].push_back(x);
}
for(int i=1, x, y; i<n; ++i){
scanf("%d%d", &x, &y);
e2[x].push_back(y); e2[y].push_back(x);
}
timer=0;
dfs1(1, 0);
for(int i=1; i<=n; ++i) vec[i].clear();
dfs2(1, 0);
ans=0;
dfs3(1, 0, 0);
printf("%lld\n", ans);
}
}
凡是值得做的事情,都值得慢慢去做,然后做很久很久,加油吧,少年

浙公网安备 33010602011771号