cf1276 D. Tree Elimination

这篇博客讨论了如何解决CF1276D问题,即树消除策略。首先,它从特殊情况开始,分析一条链上的问题,通过动态规划给出解决方案。接着,扩展到直线上的情况,引入多维DP来处理边的乱序。最后,将这种方法推广到树形结构,自下而上地进行DP,解释了如何根据边的时间戳对子树进行决策计数。博客还包含了代码实现。

链接

点击跳转

先考虑一条链上的特殊情形

如果是一条链,而且边的顺序是从后往前,那么我很容易写出 d p dp dp

定义 f i , 0 / 1 f_{i,0/1} fi,0/1为前 i i i个位置已经决策完了,并且最后一个点有没有被选,的方案数。

转移就是:

f i , 0 = f i − 1 , 0 + f i − 1 , 1 f_{i,0}=f_{i-1,0}+f_{i-1,1} fi,0=fi1,0+fi1,1
f i , 1 = f i − 1 , 0 f_{i,1}=f_{i-1,0} fi,1=fi1,0

再考虑一条直线上的一般情形

如果我给出边的顺序不是按照从前往后,而是乱序的,那么第 i i i条边所面临的决策也会受到后面的影响

看似有后效性,但其实会发现这个题目中决策只会影响到相邻的位置,因此有一个套路就是,多开一维记录后面对前面的影响

f i , 0 / 1 , 0 / 1 f_{i,0/1,0/1} fi,0/1,0/1表示决策第 i i i个位置前, i i i线段的右端点是 0 / 1 0/1 0/1,第 i i i个位置决策后, i i i线段的右端点是 0 / 1 0/1 0/1,的方案数。

由于 i , 1 , 0 i,1,0 i,1,0这个状态无效因此我把它丢掉

f i , 0 , f i , 1 , f i , 2 f_{i,0},f_{i,1},f_{i,2} fi,0,fi,1,fi,2依次表示 ( 0 → 0 ) , ( 0 → 1 ) , ( 1 → 1 ) (0 \rightarrow 0),(0 \rightarrow 1),(1 \rightarrow 1) (00),(01),(11)的方案数

假设第 i i i条边的时间戳是 t i t_i ti

那么:
t i > t i + 1 t_i>t_{i+1} ti>ti+1时,
f i , 0 = f i + 1 , 0 + f i + 1 , 1 f_{i,0} = f_{i+1,0} + f_{i+1,1} fi,0=fi+1,0+fi+1,1
f i , 1 = f i + 1 , 0 f_{i,1} = f_{i+1,0} fi,1=fi+1,0
f i , 2 = f i + 1 , 0 f_{i,2} = f_{i+1,0} fi,2=fi+1,0

t i < t i + 1 t_i<t_{i+1} ti<ti+1时,
f i , 0 = f i + 1 , 2 f_{i,0} = f_{i+1,2} fi,0=fi+1,2
f i , 1 = f i + 1 , 0 + f i + 1 , 1 f_{i,1} = f_{i+1,0} + f_{i+1,1} fi,1=fi+1,0+fi+1,1
f i , 2 = f i + 1 , 0 + f i + 1 , 1 f_{i,2} = f_{i+1,0} + f_{i+1,1} fi,2=fi+1,0+fi+1,1

最后推广到树上

推广到树上之后,就变成自下而上的 d p dp dp,叶子节点的初始状态直接就是 ( 1 , 1 , 1 ) (1,1,1) (1,1,1),而 d p i , 0 / 1 / 2 dp_{i,0/1/2} dpi,0/1/2的含义也变成了“子树中边的决策的方案数”

做法也有一点变化,就是把与 u u u直接相连的边按照时间戳排个序,转移的时候基本思想还是不变,请读者自行瞎搞搞应该就能出来了

代码

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#define iinf 0x3f3f3f3f
#define linf (1ll<<60)
#define eps 1e-8
#define maxn 200010
#define maxe 400010
#define cl(x) memset(x,0,sizeof(x))
#define rep(_,__) for(_=1;_<=(__);_++)
#define em(x) emplace(x)
#define emb(x) emplace_back(x)
#define emf(x) emplace_front(x)
#define fi first
#define se second
#define mod 998244353ll
#define de(x) cerr<<#x<<" = "<<x<<endl
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
ll read(ll x=0)
{
    ll c, f(1);
    for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-f;
    for(;isdigit(c);c=getchar())x=x*10+c-0x30;
    return f*x;
}
vector<ll> to[maxn];
ll n, dp[maxn][3], p0[maxn], p1[maxn], p2[maxn];
void dfs(ll fa, ll u)
{
    ll i, j, sz=to[u].size()-1, cnt;

    if(sz==0)
    {
        dp[u][0]=dp[u][1]=dp[u][2]=1;
        return;
    }

    for(auto v:to[u])if(v!=fa)dfs(u,v);

    p0[0]=1;
    j=0;
    for(auto v:to[u])if(v!=fa)
    {
        p0[j+1]=p0[j]*dp[v][0]%mod;
        p1[j+1]=dp[v][1];
        j++;
    }

    p2[sz+1]=1;
    j=sz;
    for(auto it=to[u].rbegin();it!=to[u].rend();it++)
    {
        auto v=*it;
        if(v!=fa)
        {
            p2[j]=p2[j+1]*dp[v][2]%mod;
            j--;
        }
    }

    cnt=0;
    for(auto v:to[u])if(v!=fa)cnt++;else break;

    rep(i,cnt)
        (dp[u][0]+=p0[i-1]*p1[i]%mod*p2[i+1])%=mod;
    (dp[u][0]+=p0[cnt]*p2[cnt+1])%=mod;
    
    for(i=cnt+1;i<=sz;i++)
        (dp[u][1]+=p0[i-1]*p1[i]%mod*p2[i+1])%=mod;
    (dp[u][1]+=p0[sz])%=mod;

    rep(i,sz)
        (dp[u][2]+=p0[i-1]*p1[i]%mod*p2[i+1])%=mod;
    (dp[u][2]+=p0[sz])%=mod;
}
int main()
{
    ll i;
    n=read();
    to[1].emb(n+1);
    to[n+1].emb(1);
    rep(i,n-1)
    {
        auto u=read(), v=read();
        to[u].emb(v);
        to[v].emb(u);
    }
    dfs(n+1,1);
    printf("%lld",dp[1][2]);
    return 0;
}
<think>我们有一个任务:使用波士顿房价数据集,应用多种特征选择方法,然后比较不同特征选择方法对回归模型性能的影响。 步骤: 1. 加载波士顿房价数据集,并构建包含特征和目标值的数据框(方便后续操作)。 2. 应用以下特征选择方法: a. 方差膨胀因子(VIF) - 注意:VIF通常用于检测多重共线性,我们可以根据VIF值过滤特征(例如,VIF>10表示高共线性,考虑移除)。 b. SelectKBest - 选择k个最好的特征(例如,使用f_regression或mutual_info_regression作为评分函数)。 c. 递归特征消除(RFE) - 递归地移除最不重要的特征。 d. 递归特征消除交叉验证(RFECV) - 使用交叉验证自动选择最优特征数量。 e. Shapley Additive Explanations(SHAP) - 使用SHAP值的重要性来选择特征(注意:SHAP本身不是直接的特征选择方法,但我们可以根据特征的重要性排序来选择特征)。 3. 对于每种特征选择方法,我们将使用选定的特征训练一个回归模型(例如,线性回归)。 4. 比较不同方法在测试集上的性能(例如,使用均方误差MSE、R^2分数)。 5. 注意:由于SHAP需要模型来解释,我们可以先训练一个模型(如树模型)得到SHAP值,然后根据重要性选择特征,再用选出的特征训练另一个模型(或相同类型)来评估。 注意:波士顿房价数据集在scikit-learn的较新版本中已被移除,但我们可以从其他地方加载(如使用sklearn.datasets.fetch_openml)或者使用波士顿房价的替代数据集。不过,为了保持一致性,我们可以使用fetch_openml来加载,或者使用其他数据集。但考虑到用户指定了波士顿房价,我们使用一个变通方法:由于在旧版本中存在,我们可以通过其他方式加载(但注意,在scikit-learn 1.2之后,波士顿房价被移除了,所以建议使用其他数据集?)。但为了任务,我们仍然可以使用它(如果环境有的话),或者使用一个替代数据集(例如加州房价数据集)?然而,用户要求使用波士顿房价,所以我们将假设环境中有这个数据集(或使用fetch_openml加载一个名为'波士顿'的数据集)。 但是,根据引用[4],用户提供的引用中有加载波士顿房价的代码,所以我们可以按照引用[4]的方式加载(但请注意,在scikit-learn 1.2之后,load_boston被移除了)。因此,我们将使用fetch_openml来加载。 步骤详细: 1. 加载数据并构建数据框 2. 划分训练集和测试集(注意:特征选择应该在训练集上进行,然后应用到测试集) 3. 特征缩放(如果需要):有些特征选择方法对缩放敏感(如基于模型的方法可能需要),所以我们可以先进行标准化。 由于任务要求多种特征选择方法,我们将分别实现: a. VIF:计算每个特征的VIF,然后移除VIF值高的特征(设定阈值)。 注意:VIF计算需要每个特征对其他特征进行回归,得到R^2,然后计算VIF=1/(1-R^2)。我们通常迭代移除VIF最高的特征直到所有特征VIF低于阈值。 b. SelectKBest:使用SelectKBest选择k个特征。我们需要指定k(例如,k=10)或者通过交叉验证选择k,但这里为了简单,我们可以设定一个k值(例如一半特征)。 c. RFE:使用RFE,需要指定一个基模型(比如线性回归)和要选择的特征数量(同样,我们可以设定为一半)。 d. RFECV:使用RFECV,同样需要基模型,但可以自动选择最优特征数量。 e. SHAP:需要先训练一个模型(比如树模型LightGBM或XGBoost,或者使用线性模型),然后计算SHAP值,得到每个特征的重要性(平均|SHAP值|),然后选择重要性最高的k个特征(这里k可以设置为和前面一样,比如一半特征,或者设定一个重要性阈值)。注意:SHAP需要额外的库shap,如果环境中没有,需要安装。 4. 对于每种特征选择方法,我们使用相同的回归模型(例如线性回归)来训练和预测,然后比较性能。 注意:由于我们使用了特征选择,每种方法会选择不同的特征子集,因此我们需要记录每种方法选择的特征,然后在相同的回归模型上评估。 我们将使用以下指标:均方误差(MSE)和决定系数(R^2)。 步骤: 1. 加载数据集 2. 数据预处理(划分、标准化) 3. 定义特征选择方法 4. 对每种方法,在训练集上选择特征,然后在测试集上变换 5. 用相同的回归器(比如线性回归)在选定的特征上训练和评估 6. 比较结果 由于SHAP不是scikit-learn内置的,我们将单独处理。 我们将使用以下包: - numpy, pandas - sklearn: 数据集、模型选择、预处理、特征选择、线性模型 - statsmodels: 用于计算VIF(因为sklearn中没有直接计算VIF的函数) - shap: 用于SHAP值计算(如果环境中没有,则尝试安装,但代码中需要检查) 注意:由于波士顿数据集被移除,我们将使用fetch_openml加载,数据集名称为'boston'。 但是,根据引用[5]的内容,引用[5]是作者介绍,与任务无关。引用[1][2][3][4]中,[4]提到了加载波士顿房价,但方法已过时。所以我们使用fetch_openml。 由于我们无法确定用户环境中是否有shap,所以我们将SHAP特征选择部分放在try-except中,如果导入失败则跳过。 另外,递归特征消除(RFE)和RFECV需要基模型,我们使用线性回归作为基模型(虽然线性回归没有特征重要性,但可以使用系数绝对值作为重要性)。但是,线性回归在RFE中通常效果不好,因为特征共线性可能导致系数不稳定。因此,我们可以考虑使用岭回归(Ridge)作为基模型。 实现步骤: 1. 导入必要的库 2. 加载数据 3. 数据预处理(划分、标准化) 4. 定义特征选择方法函数(VIF, SelectKBest, RFE, RFECV, SHAP) 5. 对每种特征选择方法,进行特征选择并训练模型(线性回归) 6. 评估并比较 注意:由于VIF方法需要迭代计算,并且每次移除一个高VIF特征,我们编写一个函数完成。 由于时间关系,我们设定一些参数: - 对于SelectKBest,我们选择k=10个特征(波士顿数据集有13个特征,所以选择10个) - 对于RFE,我们也选择10个特征 - 对于RFECV,让算法自动选择最优数量 - 对于VIF,阈值设为10(迭代移除VIF>10的特征) - 对于SHAP,我们选择重要性最高的10个特征 但是,为了公平比较,我们也可以让RFECV选择最优数量,然后我们记录该数量,但评估时使用该数量。而其他方法我们固定了特征数量(10个),这样比较可能不公平。因此,我们可以调整: - 方法1:固定特征数量(除了RFECV,因为它自动选择) - 方法2:不固定数量,但这样比较指标时需要考虑特征数量。 我们这里采用固定数量(10)来比较,除了RFECV(因为它自动选择,我们保留其选择的特征数量)。 另外,SHAP方法我们没有固定数量,所以我们也选择10个。 因此,除了RFECV,其他方法我们都选择10个特征(或VIF方法是通过阈值选择,所以数量可能不是10,我们也不固定,而是通过迭代移除VIF>10的直到所有小于10)。所以VIF方法得到的特征数量不是固定的。 为了简化,我们这样处理:对于VIF方法,我们移除所有VIF>10的特征(一次迭代移除一个,直到所有特征VIF<=10)。这样得到的特征数量可能不等。同样,其他方法我们固定选择10个特征。 然后,我们分别用线性回归模型训练,并记录测试集上的R^2和MSE,以及选定的特征数量。 开始编写代码: </think>以下是使用Scikit-learn实现波士顿房价数据集特征选择的完整Python程序,包含五种特征选择方法并比较模型性能: ```python import numpy as np import pandas as pd import matplotlib.pyplot as plt from statsmodels.stats.outliers_influence import variance_inflation_factor from sklearn.datasets import fetch_openml from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.feature_selection import SelectKBest, RFE, RFECV, f_regression from sklearn.linear_model import LinearRegression, Ridge from sklearn.metrics import r2_score, mean_squared_error from sklearn.ensemble import RandomForestRegressor import shap # 1. 加载数据集并创建数据框 boston = fetch_openml(name='boston', version=1, as_frame=True) df = pd.DataFrame(boston.data, columns=boston.feature_names) df['MEDV'] = boston.target # 添加目标变量 X = df.drop('MEDV', axis=1) y = df['MEDV'] # 2. 数据预处理 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # 3. 初始化回归模型 lr = LinearRegression() ridge = Ridge(alpha=1.0) # 4. 基准模型(无特征选择) lr.fit(X_train_scaled, y_train) y_pred_base = lr.predict(X_test_scaled) base_r2 = r2_score(y_test, y_pred_base) base_mse = mean_squared_error(y_test, y_pred_base) # 5. 特征选择方法 results = {'Method': ['Baseline'], 'R²': [base_r2], 'MSE': [base_mse]} selected_features = [] # 方法1: 方差膨胀因子(VIF) def calculate_vif(X): vif_data = pd.DataFrame() vif_data["Feature"] = X.columns vif_data["VIF"] = [variance_inflation_factor(X.values, i) for i in range(len(X.columns))] return vif_data vif_df = calculate_vif(pd.DataFrame(X_train_scaled, columns=X.columns)) low_vif_features = vif_df[vif_df['VIF'] < 5]['Feature'].tolist() # VIF阈值=5 X_train_vif = X_train_scaled[:, [X.columns.get_loc(c) for c in low_vif_features]] X_test_vif = X_test_scaled[:, [X.columns.get_loc(c) for c in low_vif_features]] lr.fit(X_train_vif, y_train) y_pred_vif = lr.predict(X_test_vif) results['Method'].append('VIF') results['R²'].append(r2_score(y_test, y_pred_vif)) results['MSE'].append(mean_squared_error(y_test, y_pred_vif)) selected_features.append(low_vif_features) # 方法2: SelectKBest selector_kbest = SelectKBest(score_func=f_regression, k=8) X_train_kbest = selector_kbest.fit_transform(X_train_scaled, y_train) X_test_kbest = selector_kbest.transform(X_test_scaled) kbest_features = X.columns[selector_kbest.get_support()].tolist() lr.fit(X_train_kbest, y_train) y_pred_kbest = lr.predict(X_test_kbest) results['Method'].append('SelectKBest') results['R²'].append(r2_score(y_test, y_pred_kbest)) results['MSE'].append(mean_squared_error(y_test, y_pred_kbest)) selected_features.append(kbest_features) # 方法3: 递归特征消除(RFE) rfe = RFE(estimator=ridge, n_features_to_select=6) X_train_rfe = rfe.fit_transform(X_train_scaled, y_train) X_test_rfe = rfe.transform(X_test_scaled) rfe_features = X.columns[rfe.support_].tolist() lr.fit(X_train_rfe, y_train) y_pred_rfe = lr.predict(X_test_rfe) results['Method'].append('RFE') results['R²'].append(r2_score(y_test, y_pred_rfe)) results['MSE'].append(mean_squared_error(y_test, y_pred_rfe)) selected_features.append(rfe_features) # 方法4: 递归特征消除交叉验证(RFECV) rfecv = RFECV(estimator=ridge, cv=5, scoring='r2') X_train_rfecv = rfecv.fit_transform(X_train_scaled, y_train) X_test_rfecv = rfecv.transform(X_test_scaled) rfecv_features = X.columns[rfecv.support_].tolist() lr.fit(X_train_rfecv, y_train) y_pred_rfecv = lr.predict(X_test_rfecv) results['Method'].append('RFECV') results['R²'].append(r2_score(y_test, y_pred_rfecv)) results['MSE'].append(mean_squared_error(y_test, y_pred_rfecv)) selected_features.append(rfecv_features) # 方法5: SHAP特征重要性 rf = RandomForestRegressor(n_estimators=100, random_state=42) rf.fit(X_train_scaled, y_train) explainer = shap.TreeExplainer(rf) shap_values = explainer.shap_values(X_train_scaled) shap_importances = np.abs(shap_values).mean(axis=0) top_shap_idx = np.argsort(shap_importances)[-7:] # 选择前7个特征 X_train_shap = X_train_scaled[:, top_shap_idx] X_test_shap = X_test_scaled[:, top_shap_idx] shap_features = X.columns[top_shap_idx].tolist() lr.fit(X_train_shap, y_train) y_pred_shap = lr.predict(X_test_shap) results['Method'].append('SHAP') results['R²'].append(r2_score(y_test, y_pred_shap)) results['MSE'].append(mean_squared_error(y_test, y_pred_shap)) selected_features.append(shap_features) # 6. 结果比较 results_df = pd.DataFrame(results) print("\n=== 模型性能比较 ===") print(results_df.round(4)) print("\n=== 各方法选择的特征 ===") for method, features in zip(results['Method'][1:], selected_features): print(f"{method}: {features}") # 7. 可视化性能比较 plt.figure(figsize=(10, 6)) plt.subplot(2, 1, 1) plt.bar(results_df['Method'], results_df['R²'], color='skyblue') plt.title('R² 分数比较') plt.ylabel('R²') plt.subplot(2, 1, 2) plt.bar(results_df['Method'], results_df['MSE'], color='salmon') plt.title('MSE 比较') plt.ylabel('MSE') plt.tight_layout() plt.show() ``` ### 程序说明: 1. **数据集加载**: - 使用`fetch_openml`加载波士顿房价数据集 - 创建包含所有特征和目标值的数据框 2. **特征选择方法**: - **方差膨胀因子(VIF)**:移除高共线性特征(VIF阈值=5) - **SelectKBest**:选择相关性最高的8个特征 - **递归特征消除(RFE)**:使用岭回归选择6个特征 - **递归特征消除交叉验证(RFECV)**:自动确定最优特征数量 - **SHAP值分析**:基于随机森林的特征重要性选择Top7特征 3. **性能评估**: - 使用$R^2$(决定系数)和MSE(均方误差)评估模型 - 基准模型使用所有原始特征 - 可视化展示各方法性能对比 4. **关键输出**: - 性能比较表格(各方法的$R^2$和MSE) - 各方法选择的特征列表 - 性能对比可视化图表 ### 运行结果分析: 1. SHAP和RFECV通常表现最佳,因其考虑了特征交互和交叉验证 2. VIF主要解决共线性问题,可能保留更多特征 3. SelectKBest计算效率高,但忽略特征间相互作用 4. RFE需要预设特征数量,而RFECV自动优化该参数 5. 特征选择通常能提高模型性能并降低过拟合风险[^1][^2] ### 注意事项: 1. 需安装额外依赖:`pip install shap statsmodels` 2. 特征数量参数(如SelectKBest的k值)可根据数据调整 3. SHAP解释器可选Tree/Deep/Kernel等不同解释器 4. 回归模型可替换为RandomForest或GBDT等更复杂模型[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值