2014.7.22 - 多校1

本文详细解析了树形DP解决无根树问题的方法,包括寻找质心、子树计数等,并通过实例讲解了概率DP在天气预测中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://blog.sina.com.cn/s/blog_6bddecdc0102uy9g.html


C 树形DP hdu4863

problem

1.一棵无根树。最多50个case,每个case最多200个点。

2.每个点有个value,去掉这个点之后剩下一或多个树,节点数最多的那颗树的节点数,就这这个点的value。

3.value最小的那个点,是整棵树的质心。

4.a.只有一个质心。b.有两个质心并且挨着。c.不会有其他情况。(题目中给的)

5.这棵无根树中,有几颗子树,其质心与正棵树的质心完全相同。

think

我是几乎完全按照官方题解写的。

1.搞出每个点的value从而得到质心。这就不多说了。

2.dp[s][j] 表示,以质点为整棵树的根(如果有两个质点就分成两棵树,具体下面说),s为子树的根,这颗子树有j个节点,的子树个数。

3.那么dp可以用背包得到。公式就是dp[s][j+k] += dp[s][j] * dp[ss][k]; 其中ss是s的孩子,j和k分别表示子树s和子树ss的节点个数。

for(int j = n; j >= 1; --j) if(dp[s][j]){
    for(int k = 1; k <= n; ++k){
        dp[s][j+k] += dp[s][j] * dp[ss][k];
        dp[s][j+k] %= mod;
    }
}
4.如果是两个质点,那么所求子树也是相同的这两个质点,只要两边的节点个数一样多就可以啦。A和B分别表示两个质点。

for(int i = 1; i <= n/2; ++i){
    ans = (ans + dp[A][i] * dp[B][i]) % mod;
}
5.如果只有一个质点。就用总的方案树减去不合理的方案数。总的方案数是:

for(int j = 1; j <= n; ++j){
    ans = (ans + dp[A][j]) % mod;
}
6.不合理的方案的冲要条件是。最大的那个儿子子树(与根直接相连的分子树)的节点数占到了整个子树的节点数的一半甚至更多。这个想一下,就是的。

7.于是不合理的方案数也和上面一样背包处理。这里先枚举那个最大分子树是哪个节点,然后背包处理除了这个子树的类似dp[A][x]的值,由于这里只找A(质点)的所以一维就可以啦,用dp2表示。然后枚举那个不可以的子树的节点个数mx,和dp2[1到mx],他俩相乘就是不可以的。这部分的代码就是solve()函数里面的。

code

const int N = 222;
const int mod = 10007;
vector<int>v[N];
int sz[N];
int val[N];
int dp[N][N];
int dp2[N];
int A, B, n;

void dfs1(int s, int pre){
    sz[s] = 1;
    int len = v[s].size();
    for(int i = 0; i < len; ++i){
        int ss = v[s][i];
        if(ss == pre) continue;
        dfs1(ss, s);
        sz[s] += sz[ss];
    }
}

void dfs2(int s, int pre){
    int len = v[s].size();
    val[s] = n - sz[s];
    for(int i = 0; i < len; ++i){
        int ss = v[s][i];
        if(ss == pre) continue;
        val[s] = max(val[s], sz[ss]);
        dfs2(ss, s);
    }
}

void find(){
    int tmp = n+10;
    A = 0, B = 0;
    for(int i = 1; i <= n; ++i){
        if(val[i] < tmp){
            A = i;
            B = 0;
            tmp = val[i];
        } else if(val[i] == tmp){
            B = i;
        }
    }
}

void dfs3(int s, int p1, int p2){
    int len = v[s].size();
    dp[s][1] = 1;
    for(int i = 0; i < len; ++i){
        int ss = v[s][i];
        if(ss == p1 || ss == p2) continue;
        dfs3(ss, s, p2);
        for(int j = n; j >= 1; --j) if(dp[s][j]){
            for(int k = 1; k <= n; ++k){
                dp[s][j+k] += dp[s][j] * dp[ss][k];
                dp[s][j+k] %= mod;
            }
        }
    }
}

int solve(){
    int ans = 0;
    for(int j = 1; j <= n; ++j){
        ans = (ans + dp[A][j]) % mod;
    }
    int len = v[A].size();
    for(int i = 0; i < len; ++i){
        int s1 = v[A][i];
        memset(dp2, 0, sizeof(dp2));
        dp2[1] = 1;
        for(int j = 0; j < len; ++j) if(i != j){
            int s2 = v[A][j];
            for(int h = n; h > 0; --h) if(dp2[h]){
                for(int k = 1; k <= n; ++k){
                    dp2[h+k] += dp2[h] * dp[s2][k];
                    dp2[h+k] %= mod;
                }
            }
        }
        int tmp = 0;
        for(int mx = 1; mx <= n; ++mx){
            for(int j = 1; j <= mx; ++j){
                tmp = (tmp + dp2[j]*dp[s1][mx]) % mod;
            }
        }
        ans = (ans - tmp + mod) % mod;
    }
    return ans;
}

int main(){
    int T, tt = 0;
    scanf("%d", &T);
    while(T--){
        scanf("%d", &n);
        for(int i = 1; i < n; ++i){
            int a, b;
            scanf("%d%d", &a, &b);
            v[a].PB(b);
            v[b].PB(a);
        }
        dfs1(1, 0); //得到每个子树的size
        dfs2(1, 0); //得到每个节点题目中描述的value
        find(); //找到质心
        int ans = 0;
        if(B){ //有两个质心
            dfs3(A, 0, B);
            dfs3(B, 0, A);
            for(int i = 1; i <= n/2; ++i){
                ans = (ans + dp[A][i] * dp[B][i]) % mod;
            }
        } else { //一个质心
            dfs3(A, 0, 0);
            ans = solve();
        }
        printf("Case %d: %d\n", ++tt, ans);
        for(int i = 1; i <= n; ++i) v[i].clear();
        memset(dp, 0, sizeof(dp));
    }
    return 0;
}


E - Peter's Hobby

problem

http://acm.hdu.edu.cn/showproblem.php?pid=4865

给你每天的叶子状态序列(4种状态),求天气状态序列(3种天气)。

已知前一天天气对后一天天气的影响,和当天天气对叶子的影响,和第一天的天气概率。

think

概率DP。

weather[i][j] 表示前一天天气是i,后一天天气是j的概率,这是题中给的矩阵。

同理leaves[i][j] 表示天气i得到叶子j的概率,和 start[i] 第一天天气是i的概率。

dp[i][j] 表示第i天天气是j的概率。

那么 dp[i][j] = max(k)(dp[i-1][k] * weather[k][j] * leaves[j][a[i]])

其中dp[1][j] = start[j] * leaves[j][a[1]]

hint,用log变成加法,因为小数一直乘下去太小了。

code

double weather[3][3] = {
    {0.5, 0.375, 0.125},
    {0.25, 0.125, 0.625},
    {0.25, 0.375, 0.375} };
double leaves[3][4] = {
    {0.6, 0.2, 0.15, 0.05},
    {0.25, 0.3, 0.2, 0.25},
    {0.05, 0.1, 0.35, 0.5} };
double start[3] = {0.63, 0.17, 0.2};
double dp[55][3];
int path[55][3];
int a[55];

inline int in(){
    char str[10];
    scanf("%s", str);
    int len = strlen(str);
    if(len == 3) return 0;
    if(len == 6) return 1;
    if(len == 4) return 2;
    return 3;
}

inline void out(int x){
    if(x == 0) puts("Sunny");
    else if(x == 1) puts("Cloudy");
    else puts("Rainy");
}

void solve(int now, int n){
    if(n > 1) solve(path[n][now], n - 1);
    out(now);
}

int main(){
    int T, tt = 0, n;
    for(int i = 0; i < 3; ++i) for(int j = 0; j < 3; ++j) weather[i][j] = log(weather[i][j]);
    for(int i = 0; i < 3; ++i) for(int j = 0; j < 4; ++j) leaves[i][j] = log(leaves[i][j]);
    for(int i = 0; i < 3; ++i) start[i] = log(start[i]);
    scanf("%d", &T);
    while(T--){
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) a[i] = in();
        for(int i = 0; i < 3; ++i) dp[1][i] = start[i] + leaves[i][a[1]];
        for(int i = 2; i <= n; ++i){
            for(int j = 0; j < 3; ++j){
                for(int k = 0; k < 3; ++k){
                    double tmp = dp[i-1][k] + weather[k][j] + leaves[j][a[i]];
                    if(k == 0 || tmp > dp[i][j]){
                        dp[i][j] = tmp;
                        path[i][j] = k;
                    }
                }
            }
        }
        int now = 0;
        double tmp = dp[n][0];
        for(int i = 1; i < 3; ++i){
            if(dp[n][i] > tmp){
                now = i;
                tmp = dp[n][i];
            }
        }
        printf("Case #%d:\n", ++tt);
        solve(now, n);
    }
    return 0;
}


资源下载链接为: https://pan.quark.cn/s/140386800631 通用大模型文本分类实践的基本原理是,借助大模型自身较强的理解和推理能力,在使用时需在prompt中明确分类任务目标,并详细解释每个类目概念,尤其要突出类目间的差别。 结合in-context learning思想,有效的prompt应包含分类任务介绍及细节、类目概念解释、每个类目对应的例子和待分类文本。但实际应用中,类目和样本较易导致prompt过长,影响大模型推理效果,因此可先通过向量检索缩小范围,再由大模型做最终决策。 具体方案为:离线时提前配置好每个类目的概念及对应样本;在线时先对给定query进行向量召回,再将召回结果交给大模型决策。 该方法不更新任何模型参数,直接使用开源模型参数。其架构参考GPT-RE并结合相关实践改写,加入上下文学习以提高准确度,还使用BGE作为向量模型,K-BERT提取文本关键词,拼接召回的相似例子作为上下文输入大模型。 代码实现上,大模型用Qwen2-7B-Instruct,Embedding采用bge-base-zh-v1.5,向量库选择milvus。分类主函数的作用是在向量库中召回相似案例,拼接prompt后输入大模型。 结果方面,使用ICL时accuracy达0.94,比bert文本分类的0.98低0.04,错误类别6个,处理时添加“家居”类别,影响不大;不使用ICL时accuracy为0.88,错误58项,可能与未修改prompt有关。 优点是无需训练即可有较好结果,例子优质、类目界限清晰时效果更佳,适合围绕通用大模型api打造工具;缺点是上限不高,仅针对一个分类任务部署大模型不划算,推理速度慢,icl的token使用,用收费api会有额外开销。 后续可优化的点是利用key-bert提取的关键词,因为核心词语有时比语意更重要。 参考资料包括
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值