Silverwolfnya

2025年9月训练记录

2025/9/25

abc416_f

题意:树上选\(k\)条不相交的链,使得其贡献和最大.

可以考虑树形\(dp\).

考虑状态的设计如下:设\(dp_{u,i,0/1/2}\)表示当前选了以\(u\)为根的子树,且当前子树的根节点的状态为\(0/1/2\)的最大子树贡献.

  • \(0:\)所选的子树不在任何一条链中
  • \(1:\)所选的子树留了向上转移的接口,可以作为一条链向上拼接
  • \(2:\)所选的子树不留向上转移的接口,不能作为链向上拼接

可以以树形背包的方式去做转移。

首先根据定义可以得到这样的转移:

  • \(dp_{u,i+j,0}\leftarrow max(dp_{u,i,0}+max(dp_{v,j,0},dp_{v,j,1},dp_{v,j,2}))\)
  • \(dp_{u,i+j,1}\leftarrow max(dp_{u,i,1}+max(dp_{v,j,0},dp_{v,j,1},dp_{v,j,2}))\)
  • \(dp_{u,i+j,2}\leftarrow max(dp_{u,i,2}+max(dp_{v,j,0},dp_{v,j,1},dp_{v,j,2}))\)

考虑拼出一条留接口的链的转移:

  • \(dp_{u,i+j,1}\leftarrow max(dp_{u,i,0}+dp_{v,j,1}+a_u)\)
image-20250925235359117

同理,考虑不留接口的向上转移,值得注意的是,你把\(u\)节点同时接入两条边,会让链数减\(1\),故转移方程如下:

  • \(dp_{u,i+j-1,2}\leftarrow max(dp_{u,i,1}+dp_{v,j,1})\)
image-20250925235834855

注意保证\(dp\)的无后效性,可以用滚动数组处理一下.

时间复杂度\(O(nk^2)\).

9.26

感觉一整天都在想这一道题来着

CF2150C

首先比较套路地,把\(\text{Bob}\)的序列处理成\([1,2,3...n]\),同步更新\(Alice\)的序列和值域序列。

模拟一下两人取数的过程,你发现,当第\(i\)个位置的\(a_i\)\(\text{Bob}\)取走后,那么满足如下条件的,\(Alice\)一定无法取得:\(j>i\and a_j<a_i\).

不妨设计\(dp_{i,j}\)表示当前考虑到了第\(i\)个位置,且被\(Bob\)取走的最大值,即\(\text{Alice}\)放弃的最大值,此时\(\text{Alice}\)取数能取的最大贡献.

设计推表转移,可以大致分为四种情况:

  • \(a_i<j\)\(a_i\),由上面得到的性质,这部分是不合法的,所以不做转移
  • \(a_i<j\)不选\(a_i\)\(dp_{i-1,j}\rightarrow dp_{i,j}\)
  • \(a_i>j\)\(a_i\)\(dp_{i-1,j}+w_{a_i}\rightarrow dp_{i,j}\)
  • \(a_i>j\)不选\(a_i\)\(max(dp_{i-1,j}+w_{a_i})\rightarrow dp_{i,a_i}\)

这样暴力转移是\(O(n^2)\)的.

但是,你发现转移分为了三段区间,\([0,a_i),a_i,(a_i,n]\),由上述转移可以发现,实际上就是对\([0,a_i)\)做区间加,对\(a_i\)做覆盖,对\((a_i,n]\)保持不变,所以可以维护一颗线段树,维护区间最值以及区间加辅助转移。

这样可以将时间复杂度优化到\(O(n\log n)\).

洗完澡补了一下网络赛的题

25CCPC网络赛C

首先问题可以直接转化成,进行\(n-1\)轮的连边,每次连边的代价是\((a_u+a_v)\% k\),问最小代价,因此把每个点的权值由\(a_i\)处理成\(a_i\% k\).

这是稠密图的最小生成树问题,我们可以用类似\(\text{Prim}\)算法的方式解决,首先考虑朴素版的算法,也就是每次枚举当前联通块,找到与当前联通块内最小的边权并加入.

本题有完全图的特殊性,而且所有边权都是可以计算得到的,对于某个点的延伸,要加入的边权,一定是当前与它没有联通的点中边权最小的那个,这样贪心是不会劣的,最小的边权的确定可以二分找第一个\(\geq k-a_u\)的位置,如没找到就加入第一个点.

我们用一个\(\text{set}\)维护当前还没有被加入大联通块的点,每次向大联通块中加入新的点时,按以上规则更新新边,这样的操作一共会进行\(n-1\)轮.

因此,我们得到\(O(n\log n)\)的做法.

2025/9/27

今天主要是和队友\(\text{vp}\).

晚上写\(\text{abc}\)状态前所未有的差.

abc_425f

考虑一个显然的状压\(dp\)如:\(dp_{i,sta}\)表示当前考虑到了第\(i\)个被放入的字符串,且当前选入的字符的二进制状态为\(sta\).

转移的时候,我们只要枚举当前状态从哪里转移过来,如果多个转移进入的状态的哈希值是相同的,只记一个就好了,那我们可以预处理\(2^n\)种不同的子序列的哈希值,然后开桶去重即可,不作任何优化,时间复杂度是\(O(n^2 2^n)\)的.

我认为本题的优化是挺启发式的,优化如下:

  • 枚举了第\(i\)个被放入的状态时,有效的状态一定满足其二进制位恰好是\(i\)位,故我们可以按二进制编码的位数从小排序,然后双指针.
  • 在转移的时候,我们只需要枚举当前状态所有二进制下的\(1\)位,可以用\(\text{lowbit}\)优化.

计算一下复杂度:
\((2^1+2^2+.....2^n)n=O(n2^{n+1})\).

常数很大,卡了过去,不知道是不是评测机波动.

代码实现如下.

/*
 *          ┏┓    ┏┓
 *          ┏┛┗━━━━━━━┛┗━━━┓
 *          ┃       ┃  
 *          ┃   ━    ┃
 *          ┃ >   < ┃
 *          ┃       ┃
 *          ┃... ⌒ ...  ┃
 *          ┃              ┃
 *          ┗━┓          ┏━┛
 *          ┃          ┃ Code is far away from bug with the animal protecting          
 *          ┃          ┃   神兽保佑,代码无bug
 *          ┃          ┃           
 *          ┃          ┃        
 *          ┃          ┃
 *          ┃          ┃           
 *          ┃          ┗━━━┓
 *          ┃              ┣┓
 *          ┃              ┏┛
 *          ┗┓┓┏━━━━━━━━┳┓┏┛
 *           ┃┫┫       ┃┫┫
 *           ┗┻┛       ┗┻┛
 */ 
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<cmath>
#include<bitset>
#include<cstring>
#include<queue>
#include<iomanip>
#define mt make_tuple
//cout << setprecision(3) ; //输出3位小数,3.142
#define pb push_back
#define fi first
#define se second
using namespace std;
using ll=long long;
using i128=__int128;
typedef pair<int,int>pii;
typedef tuple<int,int,int>ti3;
const int N=3e5+10;
//读错题了,傻逼,你是人吗
//可以任意插 显然有类似状压dp的做法
//998244353
struct Hash{
private:
const int base=1331;//具体使用可以修改成双模或双base防止hack
const i128 mod=100000000000000049;
i128 mo(i128 x){
    return (x%mod+mod)%mod;
}
public:
    int n;//长度
    i128 s=0;
    Hash(){}
    Hash(string str){//传入的参数不需要前面加空格,正常输入即可
        n=str.length();
        for(int i=1;i<=n;i++){
            s=mo(s*base+(str[i-1]-'a'+1))%mod;//这里具体问题记得修改
        }
    }
    ll get(){
        return s;
    }
};
const int mod=998244353;
int lowbit(int x){
    return x&-x;
}
void Silverwolf(){
    int n;
    cin>>n;
    string s;
    cin>>s;
    vector<int>hash(1<<n,-1);
    vector<int>lsh;
    for(int sta=1;sta<(1<<n);sta++){
        string su;
        for(int i=0;i<n;i++){
            if(sta>>i&1){
                su.pb(s[i]);
            }
        }
        hash[sta]=Hash(su).get();
        lsh.pb(hash[sta]);
    }
    lsh.pb(-1);
    lsh.pb(-2);
    sort(lsh.begin(),lsh.end());
    lsh.erase(unique(lsh.begin(),lsh.end()),lsh.end());
    for(int sta=0;sta<(1<<n);sta++){
        hash[sta]=lower_bound(lsh.begin(),lsh.end(),hash[sta])-lsh.begin();
    }
    vector<int>bit;
    for(int i=0;i<(1<<n);i++)bit.pb(i);
    sort(bit.begin(),bit.end(),[&](int x,int y){
        return __builtin_popcount(x)<__builtin_popcount(y);
    });
    vector<int>dp(1<<n,0);
    dp[0]=1;
    vector<bool>vis(1<<(n+1),false);
    int l=1;
    for(int i=1;i<=n;i++){
        for(int j=l;j<(1<<n);j++){
            l=j;
            int sta=bit[j];
            if(__builtin_popcount(sta)>i)break;
            for(int j=sta,k;j;j-=1<<k){
                k=__lg(j);
                if(!(sta>>k&1))continue;
                int nsta=sta^(1<<k);
                if(!vis[hash[nsta]]){
                    dp[sta]=(dp[sta]+dp[nsta])%mod;
                    vis[hash[nsta]]=true;
                }
            }
             for(int j=sta,k;j;j-=1<<k){
                k=__lg(j);
                if(!(sta>>k&1))continue;
                int nsta=sta^(1<<k);
                vis[hash[nsta]]=false;
            }
        }
    }
    cout<<dp[(1<<n)-1];
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    // int T;cin>>T;while(T--)
    Silverwolf();
    //愿艾利欧三度为你窥见,令你的生命永不停歇,骇入永远成功,游戏永远胜利。
    //游戏就只是为了游戏,仅此而已
    //acm这种东西,三两下搞定就好啦
    return 0;
}

[2024ICPC沈阳M]

赛时出的思路很快,但是最后没想出判非\(0\)环的方法.

考虑由\(a\% n\)\((a+b)\% n\) 建边,若能为无穷集当且仅当出现了非\(0\)权环,或当前点能够指向非\(0\)权环.

不妨缩点后跑\(dp\),那么现在问题瓶颈只在于判定\(0\)权环,我们考虑对于强连通分量内的点\(\text{bfs}\),如果出现了冲突,则说明有非\(0\)权值的环。

时间复杂度\(O(n+m+q)\),感觉代码实现的很丑。

#include<bits/stdc++.h>
//#define int long long 
#define pb push_back
#define mt make_tuple
#define fi first 
#define se second
using ll=long long;
const int N=5e5+3;
using namespace std;
typedef pair<int,int>pii;
typedef pair<string,int>psi;
typedef tuple<int,int,int>ti3;
class Tarjan{
    public:
    int n,idx,top;
    vector<int> dfn,low,col,into;
    vector<ti3> edg;
    vector<vector<int>> e;
    vector<vector<int>> scc;
    vector<vector<pii>>scc2;//把环上的边记录下来
    vector<int> stk;
    bitset<N>in;
    vector<int>tag;//标记某些位置为1
    vector<int>dp;
    //vector<int>cnt_edge;//一个联通块中的边的数量
    //vector<int>cnt_point;
    ll ans=1;
    Tarjan(int n):n(n){
        idx=0,top=0;
        e.resize(n+1);
        scc.resize(n+1);
        scc2.resize(n+1);
        dfn.assign(n+1,0);
        low.assign(n+1,0);
        col.assign(n+1,0);
        into.assign(n+1,0);
        stk.assign(n+1,0);
        //in.assign(n+1,0);
        tag.assign(n+1,0);
        dp.assign(n+1,0);
        //cnt_edge.assign(n+1,0);
        //cnt_point.assign(n+1,0);
    }
    void add(int u,int v,int w){
        e[u].pb(v);
        edg.pb(mt(u,v,w));
    }
    void tarjan(){
        for(int i=0;i<n;++i){
            if(!dfn[i])
                tarjan(i);
        }
    }
    void tarjan(int u){
        dfn[u]=low[u]=++idx;
        stk[++top]=u;
        in[u]=true;
        for(int &v:e[u]){
            if(!dfn[v])
                tarjan(v),low[u]=min(low[u],low[v]);
            else
                if(in[v])
                    low[u]=min(low[u],dfn[v]);
        }

        if(dfn[u]==low[u]){
            while(stk[top]!=u){
                in[stk[top]]=false;
                col[stk[top--]]=u;
                //cnt_point[u]++;
            }
            col[stk[top]]=u;
            //cnt_point[u]++;
            in[stk[top--]]=false;
        }
    }
    void check_cir(){
        vector<bool>vis(n+1,false);//bfs 一下
        vector<ll>dis(n+1,0);
        auto bfs=[&](int u)->bool{//判是否有非零环
            queue<int>q;
            q.push(u);
            vis[u]=true;
            while(q.size()){
                //assert(q.size()<5e7);
                auto now=q.front();
                q.pop();
                for(auto &[v,w]:scc2[now]){
                    if(!vis[v]){
                        vis[v]=true;
                        q.push(v);
                        dis[v]=dis[now]+w;
                    }else{
                        if(dis[now]+w!=dis[v]){
                            return true;
                        }
                    }
                }
            }
            return false;
        };
        for(int i=0;i<n;i++){
            if(col[i]==i){
                bool ok=bfs(i);
                if(ok)tag[i]=1;
            }
        }

    }
    void get_scc(){
        for(auto &[u,v,w]:edg){
            // cout<<col[u]<<' '<<col[v]<<'\n';
            if(col[u]==col[v]){
                // tag[col[u]]+=w;
                scc2[u].pb({v,w});
                // scc2[v].pb({u,w});
                //cnt_edge[col[u]]++;
            }
            else{
                scc[col[v]].pb(col[u]);//建反边
                into[col[u]]++;
            }
        }
       
        
    }
    void DP(){
        queue<int>q;
        for(int i=0;i<n;i++){
            if(col[i]==i&&into[i]==0)q.push(i);
        }
        while(q.size()){
            //assert(q.size()<5e7);
            auto u=q.front();
            q.pop();
            dp[u]=max(dp[u],tag[u]);
            for(int v:scc[u]){
                dp[v]=max(dp[v],dp[u]);
                if(--into[v]==0)q.push(v);
            }
        }
    }
    void work(){
        tarjan();
        get_scc();
        check_cir();
        DP();
        // for(int i=0;i<n;i++)cout<<cnt[i]<<' ';cout<<"\n";
        // for(int i=0;i<n;i++)cout<<col[i]<<' ';cout<<'\n';
        // for(int i=0;i<n;i++)cout<<tag[i]<<' ';cout<<"\n";
        // for(int i=0;i<n;i++)cout<<dp[i]<<' ';cout<<"\n";
    }
};

int get(int x,int m){
    return (x%m+m)%m;
}
void Silverwolf(){
    int n,m,q;
    cin>>n>>m>>q;
    Tarjan ta(n);
    for(int i=1;i<=m;i++){
        int a,b;
        cin>>a>>b;
        if(b==0)continue;
        int u=get(a,n);
        int v=get(a+b,n);
        // cout<<u<<' '<<v<<"\n";
        ta.add(u,v,b);
        
    }
    ta.work();
    for(int i=1;i<=q;i++){
        int u;cin>>u;
        u=get(u,n);
        if(ta.dp[ta.col[u]])cout<<"Yes\n";
        else cout<<"No\n";
    }
    
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    // int T;cin>>T;while(T--)
    Silverwolf();
    return 0;
}

2025/9/29

2024杭州站M

首先转化一下问题.

对于每个区间\([l,r]\),设最小值的位置在\(pos\),都满足\((a_{pos+k})|(a_j+k)[l\leq j\leq r ]\)成立.

每个区间都当且仅与最小值有关,考虑值域分治,每次首先找到最小值的位置\(pos\),并划分其统辖区间\([l,r]\).

上述式子可以做一个如下等价:

\[gcd(a_l+k,a_{l+1}+k+,....,a_r+k)=\\gcd(a_{l+1}-a_{l},a_{l+2}-a_{l+1},....,a_{r}-a_{r-1},a_{pos}+k)=\\a_{pos}+k \]

\(a_{pos}+k\)必须是\(gcd(a_{l+1}-a_{l},a_{l+2}-a_{l+1},....,a_{r}-a_{r-1})\)的因子

递归上述过程,可以用笛卡树实现,用\(ST\)表快速维护区间内的\(\text{gcd}\)即可.

时间复杂度为\(O(n\log n\log A+nd(A))\),其中\(d(x)\)表示因子数量.

代码实现的真的非常丑。板子抄错猛猛\(\text{WA}\).

#include<bits/stdc++.h>
#define mt make_tuple
#define fi first
#define se second
using ll=long long;
using namespace std;
typedef pair<int,int>pii;
typedef tuple<int,int,int>ti3;
//最值分治听起来挺有道理的
//最值分治+区间gcd 之类的东西
template<class T>
class ST{
public:
    vector<vector<T>>gd;
    vector<T>a;
    vector<int>lg2;
    int n;
    ST(int n,vector<T>a):n(n),a(a){
        gd.assign(20,vector<T>(n+1,0));
        // mn.assign(20,vector<T>(n+1,0));
        lg2.assign(n+1,-1);
        for(int i=1;i<=n;i++)lg2[i]=lg2[i/2]+1;
        int k=lg2[n];
        for(int i=0;i<=n;i++){
            gd[0][i]=gd[0][i]=a[i];
        }
        for(int j=1;j<=k;j++){
            for(int i=0;i<=n-(1<<j)+1;i++){
                gd[j][i]=__gcd(gd[j-1][i],gd[j-1][i+(1<<(j-1))]);
            }
        }
    }
    T Gcd(int l,int r){
        int k=lg2[r-l+1];
        return __gcd(gd[k][l],gd[k][r-(1<<k)+1]);
    }
};
const int inf=1e9+7;
class CatlanTree{//笛卡尔树板子 默认以最小值为根
public:
    vector<int>p,fa;
    vector<vector<int>>ch;
    int n,root,k;
    CatlanTree(){
        cin>>n>>k;
        p.resize(n+1);
        fa.assign(n+1,0);
        ch.assign(n+1,vector<int>(2,0));
        for(int i=1;i<=n;i++)cin>>p[i];
        vector<int>stk(n+2);
        int top=0;
        for(int i=1;i<=n;i++){
            int lst=0;
            while(top&&p[stk[top]]>=p[i]){
                lst=stk[top];
                top--;
                
            }
            if(top){
                fa[i]=stk[top];
                ch[stk[top]][1]=i;
            }
            if(lst){
                ch[i][0]=lst;
                fa[lst]=i;
            }
            stk[++top]=i;
        }
        for(int i=1;i<=n;i++)if(fa[i]==0)root=i;
    }
    void work(){
        vector<int>cha(n+1,0);
        set<int>val;
        for(int i=2;i<=n;i++)cha[i]=abs(p[i]-p[i-1]);
        ST<int> st(n,cha);
        bool ok=true;
        auto dfs=[&](auto &&dfs,int u)->ti3{
            if(u==0)return mt(n,0,0);
            auto [l1,r1,mn1]=dfs(dfs,ch[u][0]);
            auto [l2,r2,mn2]=dfs(dfs,ch[u][1]);
            int l=min({l1,l2,u});
            int r=max({r1,r2,u});
            int len=r-l+1;
            if(len!=1){
                int gcd=st.Gcd(l+1,r);
                //枚举gcd的因子吧,感觉
                int mn=p[u];
                if((mn1==0||mn==mn1)&&(mn==mn2||mn2==0)){
                    return {l,r,p[u]};
                }
                if(ok){
                    //枚举因子
                    for(int x=1;x<=gcd/x;x++){
                        if(gcd%x==0){
                            if(x-mn>0)val.insert(x-mn);
                            if(gcd/x-mn>0)val.insert(gcd/x-mn);
                        }
                    }
                    ok=false;
                }
                vector<int>del;//待删除对象
                for(auto x:val){
                    if(__gcd(gcd,mn+x)!=mn+x)del.push_back(x);
                }
                for(auto d:del)val.erase(d);
                
            }
            return {l,r,p[u]};
        };
        
        dfs(dfs,root);
        ll ans=0,cnt=0;
        int mn=*min_element(p.begin()+1,p.end());
        int mx=*max_element(p.begin()+1,p.end());
        if(ok){
            //特判全相等
            cnt=k;
            ans=1ll*k*(k+1)/2;
        }
        for(auto x:val){
            if(x<=k){
                cnt++;
                ans+=x;
            }
        }
        cout<<cnt<<' '<<ans<<'\n';

    }
};
void Silverwolf(){
    CatlanTree tr;
    tr.work();
    // int n,k;
    // cin>>n>>k;
    // vector<int>a(n+1,0);
    // for(int i=1;i<=n;i++)cin>>a[i];
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int T;cin>>T;while(T--)
    Silverwolf();
    return 0;
}

2024成都I

首先,将\(a_i>a_{i+1}\)的位置标记成\(1\),其中\(1\leq i<n\),发现任意一个段中除了段尾可以被标记成\(1\),其他位置都不能被标记成\(1\),注意对于每个\(k\),其段尾的位置一定是\(k,2k,3k....\lfloor\frac{n}{k}\rfloor k\),因此,对于一个被标记为\(1\)的位置\(p\),其可能合法的位置有且仅有\(p\)的因子,若有很多这样的位置,维护出其\(\text{gcd}\)后枚举因子即可.

动态维护\(gcd\),用线段树维护,时间复杂度\(O(q\log n)\).

枚举因子\(O(q\sqrt n)\).

到这里本题也已经可以通过了.

可以欧拉筛优化一下,这样复杂度就是\(O(q\log n)\)了.

今天还零零散散写了几道题,感觉比较水,不想写题解了(

posted on 2025-09-26 00:11  __Silverwolf  阅读(24)  评论(0)    收藏  举报

导航