yuwj  

先看补题进度:

赛时一题没过,第一题狂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);
    }
}

凡是值得做的事情,都值得慢慢去做,然后做很久很久,加油吧,少年

posted on 2025-04-27 17:23  xiaowang524  阅读(258)  评论(0)    收藏  举报