2023牛客暑期多校训练营6 AB「范德蒙德卷积」「并查集+树上背包」

B-Distance_2023牛客暑期多校训练营6 (nowcoder.com)

题意:

定义 C ( A , B ) C(A,B) C(A,B)为:给定两个集合,每次操作可以选取 A A A B B B中的一个元素+1,让 A A A B B B相等的最少操作数,若无法实现则为0,现给定集合ST,求 ∑ A ⊆ S ∑ B ⊆ T C ( A , B ) \sum_{A\subseteq S}\sum_{B\subseteq T}C(A,B) ASBTC(A,B)

思路:

最小的操作一定是排序后一一对应的操作。考虑枚举每一对 ( i , j ) (i,j) (i,j),计算 a b s ( s [ i ] − t [ j ] ) abs(s[i]-t[j]) abs(s[i]t[j])的贡献,每一对的贡献是当他为集合中的某一个元素时,所有的集合数量*该对的贡献。集合数量为 ∑ k = 1 k = m i n ( i − 1 , j − 1 ) ∑ c = 1 c = m i n ( n − i , n − j ) C ( i − 1 , k ) × C ( j − 1 , k ) × C ( n − i , c ) × C ( n − j , c ) \sum_{k=1}^{k=min(i-1,j-1)}\sum_{c=1}^{c=min(n-i,n-j)}C(i-1,k)\times C(j-1,k)\times C(n-i,c)\times C(n-j,c) k=1k=min(i1,j1)c=1c=min(ni,nj)C(i1,k)×C(j1,k)×C(ni,c)×C(nj,c),根据范德蒙德卷积 - OI Wiki (oi-wiki.org)推论4,即 ∑ i = 0 m C ( n , i ) × C ( m , i ) = C ( n + m , m ) \sum_{i=0}^m C(n,i)\times C(m,i)=C(n+m,m) i=0mC(n,i)×C(m,i)=C(n+m,m)可以化简上式为 C ( i − 1 + j − 1 , m i n ( i − 1 , j − 1 ) ) × C ( n − i + n − j , m i n ( n − i , n − j ) ) C(i-1+j-1,min(i-1,j-1))\times C(n-i+n-j,min(n-i,n-j)) C(i1+j1,min(i1,j1))×C(ni+nj,min(ni,nj)),预处理后可 O ( n 2 ) O(n^2) O(n2)求得答案。

AC代码:
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
const int mod = 998244353;
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 2e3+ 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
int s[N],t[N];
int fac[2*N],inv[2*N];

void init(){
    fac[0]=fac[1]=inv[0]=inv[1]=1;
    for(int i=2;i<=2*N;++i){
        fac[i]=1ll*fac[i-1]*i%mod;  //阶乘
        inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;  //逆元
    }
    for(int i=2;i<=2*N;++i){
        inv[i]=1ll*inv[i]*inv[i-1]%mod;
    }
}

ll C(int a,int b){
    if(a<b or a<0 or b<0)
    return 0;
    return 1ll*fac[a]*inv[b]%mod*inv[a-b]%mod;
}

void work() {
    int n;cin>>n;
    init();
    for(int i=1;i<=n;++i){
        cin>>s[i];
    }for(int i=1;i<=n;++i){
        cin>>t[i];
    }
    ll ans=0;
    for(int i=1;i<=n;++i){
        for(int j=1;j<=n;++j){
            int x=abs(s[i]-t[j]);
            ll res=x%mod*C(i-1+j-1,min(i,j)-1)%mod*C(n-i+n-j,min(n-i,n-j))%mod;
            ans=(ans+res)%mod;
        }
    }
    cout<<ans<<'\n';
}

signed main() {
    io;
    int t=1;
    //cin >> t;
    while (t--) {
        work();
    }
    return 0;
}

A-Tree_2023牛客暑期多校训练营6 (nowcoder.com)

题意:

给定一颗边带权的树,树上每个节点都有一个颜色(0/1),颜色可以通过 c [ i ] c[i] c[i]的代价翻转,两个异色点的贡献是路径上边权最大值,同色点没有贡献,问经过任意翻转后,树的贡献-代价的最大值。

思路:

按照边权排序,重构最大生成树,对于每对点 ( u , v ) (u,v) (u,v),若异色,贡献值即为当前的边权,能使当前边权的做出贡献的点对数量即uv的子树中相互异色的点相乘
d p [ i ] [ j ] dp[i][j] dp[i][j]表示以i为根节点,树中有j个黑色点的最大答案,转移时 d p [ u ] [ j + k ] = m a x ( d p [ u ] [ j ] + d p [ v ] [ k ] + w ∗ ( ( s z [ u ] − j ) ∗ k + j ∗ ( s z [ v ] − k ) ) dp[u][j+k]=max(dp[u][j]+dp[v][k]+w*((sz[u]-j)*k+j*(sz[v]-k)) dp[u][j+k]=max(dp[u][j]+dp[v][k]+w((sz[u]j)k+j(sz[v]k)),树上背包 即可,最后遍历最终根节点的黑色点数即可求得答案。

AC代码:
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
const int mod = 998244353;
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 3e3+ 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
int a[N],f[N],c[N],sz[N];
ll tmp[N],dp[N][N];//黑色
struct node{
    int u,v,w;
}e[N];

int find(int x){
    if(f[x]==x)return x;
    return f[x]=find(f[x]);
}

void work() {
    int n;cin>>n;
    for(int i=1;i<=n;++i){
        cin>>a[i];f[i]=i;sz[i]=1;
    }
    for(int i=1;i<=n;++i){
        cin>>c[i];
        if(a[i]){//黑色
            dp[i][0]=-c[i];
        }else{
            dp[i][1]=-c[i];
        }
    }
    for(int i=1;i<n;++i){
        cin>>e[i].u>>e[i].v>>e[i].w;
    }
    sort(e+1,e+n,[](node &x,node &y)->bool {
        return x.w<y.w;
    });
    for(int i=1;i<n;++i){
        int u=find(e[i].u),v=find(e[i].v);
        mem(tmp,-0x3f3f3f3f);
        for(int j=0;j<=sz[u];++j){
            for(int k=0;k<=sz[v];++k){
                tmp[j+k]=max(tmp[j+k],dp[u][j]+dp[v][k]+e[i].w*(j*(sz[v]-k)+(sz[u]-j)*k));
            }
        }
        for(int j=0;j<=sz[u]+sz[v];++j){
            dp[u][j]=tmp[j];
        }
        sz[u]+=sz[v];f[v]=u;
    }
    int root=find(1);
    ll ans=0;
    for(int i=1;i<=n;++i){
        ans=max(ans,dp[root][i]);
    }
    cout<<ans<<'\n';
}

signed main() {
    io;
    int t=1;
    //cin >> t;
    while (t--) {
        work();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值