(待完成)学习记录-一些树的问题

发布时间 2023-04-13 20:25:20作者: xishuiw

慢慢练习一些树的思维题 大部分是补的 留给自己复习

A  Problem - 1324F - Codeforces

题意: 选择一些连通子树 让白子-黑子 的数量最大化

思路: 上下两遍扫描 处理出子树的最大值 然后再处理往上的最大值

#include<bits/stdc++.h>
#define close     std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long ll;
const ll MAXN = 3e5+7;
const ll mod =1e9+7;
const ll inf =0x3f3f3f3f;
const ll INF =0x3f3f3f3f3f3f3f3f;
//如果 cnt1>cnt2 就加上子树的贡献  
int dp[MAXN]; //dp[i]表示以i为根的树 可以存在的最大值
//转移方程 : 向下:如果子节点的>0 就加上
//              向上: 如果父节点的子节点>0 就取最大值 否则取加上和不加上的最大值 
vector<int> adj[MAXN];
void dfs_down(int u,int fa){
    for(auto v:adj[u]){
        if(v==fa)continue;
        dfs_down(v,u);
        if(dp[v]>0) dp[u]+=dp[v];
    }
}
void dfs_up(int u,int fa){
    for(auto v:adj[u]){
        if(v==fa) continue;
        if(dp[v]>0) dp[v]=max(dp[u],dp[v]);
        else dp[v]=max(dp[v],dp[u]+dp[v]);
        dfs_up(v,u);
    }
}
void solve(){
    int n;cin>>n;
    for(int i=1;i<=n;i++) {
        int a;cin>>a;
        if(a==0) dp[i]=-1;
        else dp[i]=1;
    }    
    for(int i=1;i<n;i++){
        int u,v;cin>>u>>v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    dfs_down(1,-1);
    dfs_up(1,-1);
    for(int i=1;i<=n;i++) cout<<dp[i]<<" ";
}
int main(){
    solve();
}
View Code

树上Minmax - 问题 - ZSTUOJ

题意: 这题与A比较相似 但是是处理删除后的最大值并且使删除的操作数量更少 难度更大一点

思路:dp[i][1]为以i为根节点的子树中 删除最少的点能达到的最大值

            dp[i][0]为最小步数 

    只进行一次搜索就可以完成

    如果>0 肯定要加上 <0 肯定要切割

    如果等于0 已经操作次>1 就删掉 这样赚 否则加上

    最后的时候 因为建树的时候是以1为节点 如果以其他为节点的时候 还要多删一下 因此操作数+1

#include<bits/stdc++.h>
#define close   std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long ll;
const ll MAXN = 4e5+7;
const ll mod =1e9+7;
const ll inf =0x3f3f3f3f;
const ll INF =0x3f3f3f3f3f3f3f3f;
#define int long long
vector<int > adj[MAXN];
int dp[MAXN][2];//dp[i][0]表示 包含i的i子树 操作次数 dp[i][1]表示操作后的最大权值和 
int a[MAXN];
void dfs(int u,int fa){
    dp[u][0]=0,dp[u][1]=a[u];
    for(auto &v:adj[u]){
        if(v==fa) continue;
        dfs(v,u);
        if(dp[v][1]<0){
            dp[u][0]+=1;
        }
        else if(dp[v][1]>0){
            dp[u][0]+=dp[v][0];
            dp[u][1]+=dp[v][1];
        }
        else{//=0的情况 如果v的操作数>1 删掉 否则加上去 
            if(dp[v][0]>=1){
                dp[u][0]+=1;
            }
            else{
                dp[u][0]+=dp[v][0];
                dp[u][1]+=dp[v][1];
            }
        }
    }
//  if(adj[u].size()==1) 
}
void solve(){
    int n;cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<n;i++){
        int u,v;cin>>u>>v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    dfs(1,-1);
    int maxs=dp[1][1],maxt=dp[1][0];
    for(int i=2;i<=n;i++){
        if(dp[i][1]>maxs||(dp[i][1]==maxs&&dp[i][0]+1<maxt)){//更新最小操作数和最大权值和 
            maxs=dp[i][1];
            maxt=dp[i][0]+1;
        }
    }
//  for(int i=1;i<=n;i++){
//      cout<<"# "<<i<<" : "<<" t:"<<dp[i][0]<<" x:"<<dp[i][1]<<"\n";
//  }
//  if(maxs==dp[1][1]&&maxt==dp[1][0]){//如果不是在1这个点取到的 操作数+1 
        cout<<maxs<<" "<<maxt;
//  }
//  else cout<<maxs<<" "<<maxt+1;
}
signed main(){
    solve();
}
View Code

篠塚真佑実的树 - 问题 - ZSTUOJ

题意:最多可使用一次传送门 算出最短距离

思路:赛场上是把所有点搞到一起 然后用lca算距离 一直re 赛后学长提醒才发现树有环了 寄

不过想想这道题还是比较简单的 两个点相通有两个方法 1使用传送门 2不使用传送门

使用的情况 算出每个点到传送点的最大距离 然后距离相加 (或者建虚点 dij也行

不使用的情况 直接d[u]+d[v]-d[lca(u,v)] 取最小即可

#include<bits/stdc++.h>
#define close   std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long ll;
const ll MAXN = 2e5+7;
const ll mod =1e9+7;
const ll inf =0x3f3f3f3f;
const ll INF =0x3f3f3f3f3f3f3f3f;
#define int long long
int vis[MAXN],f[MAXN];
vector<pair<int,ll> > adj[MAXN];
int par[MAXN][20],dep[MAXN];ll dis[MAXN],d[MAXN];
void dfs(int u,int fa){
    dep[u]=dep[fa]+1;
    par[u][0]=fa;
    for(int i=1;i<20;++i){
        par[u][i]=par[par[u][i-1]][i-1];
    }
    for(auto &i:adj[u]){
        int v=i.first;
        if(v==fa) continue;
        dfs(v,u);
    }
}
int getLCA(int u,int v){
    if(dep[u]<dep[v]) swap(u,v);
    for(int i=19;i>=0;--i){
        if(dep[par[u][i]]>=dep[v]) u=par[u][i];
    }
    if(u==v) return u;
    for(int i=19;i>=0;i--){
        if(par[u][i]!=par[v][i]){
            u=par[u][i];
            v=par[v][i];
        }
    }
    return par[u][0];
}
void dfs_down(int u,int fa){
    for(auto &i:adj[u]){
        int v=i.first,w=i.second;
        if(v==fa) continue;
        dfs_down(v,u);
        //if(f[v]) dis[u]=0;
        dis[u]=min(dis[u],dis[v]+w);
    }
}
void dfs_up(int u,int fa){
    for(auto &i:adj[u]){
        int v=i.first,w=i.second;
        if(v==fa) continue;
    //  if(f[u]) dis[v]=0;
        dis[v]=min(dis[v],dis[u]+w); 
        dfs_up(v,u);
    }
}
void dfs_sum(int u,int fa){
    for(auto &i:adj[u]){
        int v=i.first,w=i.second;
        if(v==fa) continue;
        d[v]=d[u]+w; 
        dfs_sum(v,u);
    } 
}
void solve(){
    int m,n;cin>>n>>m;
    int root;
    memset(dis,0x3f3f3f3f,sizeof(dis));
    memset(d,0x3f3f3f3f,sizeof(d));
    for(int i=0;i<m;i++){
        int a;cin>>a;
        f[a]=1;
        if(i==0) root=a;  
        dis[a]=0;
    }
    for(int i=1;i<n;i++){
        int u,v,w;cin>>u>>v>>w;
        adj[u].push_back({v,w});
        adj[v].push_back({u,w});
    }
 
    d[root]=0;
    dfs_sum(root,-1);
    dfs(root,-1);
    dfs_down(root,-1);
    dfs_up(root,-1);
    int q;cin>>q;
    while(q--){
        int u,v;cin>>u>>v;
        int ans=d[u]+d[v]-2*d[getLCA(u,v)];
        ans=min(ans,dis[u]+dis[v]);
        cout<<ans<<"\n";
    }
}
signed main(){
    close;
    solve();
}
View Code

Problem - 1363C - Codeforces

题意:每人轮流取一个点 谁先取到x谁输

思路:如果只有一个点 先手输 如果两个点 后手输 剩下判断奇偶性即可

#include<bits/stdc++.h>
#define close     std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long ll;
const ll MAXN = 3e5+7;
const ll mod =1e9+7;
const ll inf =0x3f3f3f3f;
const ll INF =0x3f3f3f3f3f3f3f3f;
vector<int> adj[MAXN]; 
int IN[MAXN];
int size[MAXN];
void dfs(int u,int fa){
    size[u]=1;
    for(auto v:adj[u]){
        if(v==fa) continue;
        size[u]+=size[v];
    }
}
void solve(){
    int n,x;cin>>n>>x;
    for(int i=1;i<=n;i++) size[i]=0,IN[i]=0;
    for(int i=1;i<n;i++){
        int u,v;cin>>u>>v;
        adj[u].push_back(v);
        adj[v].push_back(u);
        IN[u]++;
        IN[v]++;
    }
    if(IN[x]==1||IN[x]==0){
        cout<<"Ayush"<<"\n";
        return;
    }
//    dfs(1,-1);
    int maxs=0;

    if(n%2==0){
        cout<<"Ayush\n";
    
    }
        else cout<<"Ashish\n";
}
int main(){
    int t;cin>>t;
    while(t--)
    solve();
}
View Code

Problem - 1404B - Codeforces

题意:A和B各在一个点上 分别一步可以走da与db A先走 如果A能追上B A胜 否则B

思路:首先如果B在A的一次控制范围 即d(a,b)<=da A必胜

           如果da*2>=树的直径 那么只要A站直径中间 B逃不走

   如果da*2>=db da可以慢慢逼近db //这里还不会证明 

      写起来简单