2023杭电多校1部分题解

1002:City Upgrading

题意:

给定一颗树,每个点有一个价格,现在要在点上部署路由器使得所有点都能收到信号,点上部署路由器需要支付当前点上的价格,每个路由器可以让当前点和相邻节点收到信号,问让全部节点都收到信号的最小花费。

考点:树形dp

思路:当最开始看到这个题的时候最先想到了285. 没有上司的舞会 - AcWing题库

这一题,就是树形dp的板子题,然后按照这一题的代码改了一下就出来了第一版,但是因为仿照没有上司的舞会用了两个状态:安装路由器和不安装路由器导致有情况没有被包含所以wa了。

未考虑的情况就是:安-不安-不安-安,这种情况没法只用两种状态表示。

错误代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include <map>
#include <vector>
#include <cmath>
#define endl "\n"
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)

using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
typedef pair<int,PII> PIII;
#define int LL
const int N=200010,mod=998244353;

int mon[N];
int n;
int h[N],e[N],ne[N],idx;
bool has_fa[N];
int dp[N][2];

void add(int a,int b){
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}

void dfs(int u){
    dp[u][1]=mon[u];
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        dfs(j);
        dp[u][1]+=min(dp[j][0],dp[j][1]);
        dp[u][0]+=dp[j][1];
    }
}

void solve(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>mon[i];
    }
    for(int i=1;i<n;i++){
        int a,b;
        cin>>a>>b;
        add(a,b);
        has_fa[b]=1;
        has_fa[a]=1;
    }
    int rool=1;
    has_fa[rool]=0;
    dfs(rool);
    cout<<min(dp[rool][0],dp[rool][1])<<endl;
}

signed main(){
    quick_cin();
    int t;
    cin>>t;
    while(t--){
        idx=0;
        memset(h,-1,sizeof h);
        memset(dp,0,sizeof dp);
        solve();
    }

    return 0;
}

比赛时倒是想到增加状态了,但是一直没想对,比赛后看了标程才知道,其实只要将原来的不安装分成两种状态即可:1、不安装但是可以被其他路由器包含,2、不安装但是不能被其他路由器包含。

然后参照标程给的转移方程终于做对了QAQ:

#include<iostream>
#include<cstring>
#include<algorithm>
#include <map>
#include <vector>
#include <cmath>
#define endl "\n"
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)

using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
typedef pair<int,PII> PIII;
#define int LL
const int N=200010,mod=998244353,INF=0x3f3f3f3f3f3f3f3f;

int mon[N];
int n;
int h[N],e[N],ne[N],idx;
int dp[N][3];//0:不安装但是不能被其他路由器包含 1:安装 2:不安装但是可以被其他路由器包含,012状态的基础都是子树已经合法的被全覆盖

void add(int a,int b){
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}

void dfs(int u,int fa){
    dp[u][0]=0,dp[u][2]=INF;//状态2初始要设置成inf,要是设为0会违背状态的定义,也就是子树并没有被全覆盖
    dp[u][1]=mon[u];
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(j==fa) continue;
        dfs(j,u);
        dp[u][2]=min(dp[u][2]+min(dp[j][1],dp[j][2]),dp[j][1]+dp[u][0]);
        dp[u][1]+=min(dp[j][0],min(dp[j][1],dp[j][2]));
        dp[u][0]+=min(dp[j][1],dp[j][2]);
    }
}

void solve(){
    memset(h,-1,sizeof h);
    idx=0;
    memset(dp,0,sizeof dp);
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>mon[i];
    }
    for(int i=1;i<n;i++){
        int a,b;
        cin>>a>>b;
        add(a,b);
        add(b,a);
    }
    dfs(1,0);
    cout<<min(dp[1][1],dp[1][2])<<endl;
}

signed main(){
    quick_cin();
    int t;
    cin>>t;
    while(t--){
        solve();
    }
    return 0;
}

1012:Play on Tree

题意:俩人在一颗树上玩删点游戏,每次删除会把选中的点以及其子树删除,并且树的根节点在玩游戏前是未知的,问先手胜出的概率的逆元。

考点:SG函数、博弈论

wa原因:比赛时想到了是博弈论、也想到了nim游戏,但是后来思路偏了,一直在找奇偶点的关系,没想到sg函数。

正解思路:

首先我们可以看出,在树根固定的情况下,只有必胜或者必败两种状态。然后从叶子节点开始考虑,首先,叶子节点是先手必败,所以其sg函数的值为0,然后再考虑其父节点,因为存在叶子节点,所以其父节点的sg函数值应该为所有子节点sg函数值+1的异或(所用到的公式:sg(x+y)=sg(x)^sg(y)两个子树合成一个大子树、sg(x)=mex{sg(y)|x->y}求sg(x)的值)通过这样的思路先将以点1为根的sg值以及各个子树的sg值算出来,然后再通过异或的性质进行换根操作(详情看代码),即可得出所有点的sg值

#include<iostream>
#include<cstring>
#include<algorithm>
#include <map>
#include <vector>
#include <cmath>
#define endl "\n"
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)

using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
typedef pair<int,PII> PIII;
#define int LL
const int N=200010,mod=1e9+7,INF=0x3f3f3f3f3f3f3f3f;

int sg[N],sg_all[N];
int n;
int h[N],e[N*2],ne[N*2],idx;

LL qmi(int a, int b, int p)
{
    LL res = 1;
    while(b){
        if(b & 1) res = res * a % p;
        a = (LL)a * a % p;
        b >>= 1;
    }
    return res;
}

void add(int a,int b){
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}

void dfs(int u,int fa){
    int sg_now=0;
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(j==fa) continue;
        dfs(j,u);
        sg_now^=(sg[j]+1);
    }
    sg[u]=sg_now;
}

void dfs2(int u,int fa){
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(j==fa) continue;
        sg_all[j]=sg[j]^((sg_all[u]^(sg[j]+1))+1);//换根
        dfs2(j,u);
    }
}

void solve(){
    memset(h,-1,sizeof h);
    idx=0;
    cin>>n;
    for(int i=1;i<n;i++){
        int a,b;
        cin>>a>>b;
        add(a,b);
        add(b,a);
    }
    dfs(1,1);
    sg_all[1]=sg[1];
    dfs2(1,1);
    int ans=0;
    for(int i=1;i<=n;i++){
        if(sg_all[i]) ans++;
    }
    cout<<(ans*qmi(n,mod-2,mod))%mod<<endl;
}

signed main(){
    quick_cin();
    int t;
    cin>>t;
    while(t--){
        solve();
    }
    return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值