【百度之星2022】星球联通/原地传送

前言:

新的百度之星就要开始了,所以来补一下往年题,并分享一些刚刚补好的题的题解,也祝愿大家能够取得一个好成绩。

星球联通

题目:有 n 个星球分布在三维空间里,现在需要你将它们连接起来,连接两个星球的代价为它们距离的平方。然而,一家公司推出了新产品:传送门,这家公司愿意免费为你连接任意 k 个星球,你也可以花费 c[i] 的代价额外在 i 个星球上安装传送门使得这 k+i 个星球互相连通,现在希望知道连接这 n 个星球的最小花费。

题意:这个题意非常的绕,我也是看了其他人写的题解才明白题意。首先很明显这题是跟最小生成树有关,最小生成树是让n个顶点互相连通,题目里面就是可以免费选k个顶点使其互相连通,剩下的n-k个顶点需要花费代价来联通,有两种方式,一个是手动以距离平方为代价连接两个星球,还有一种是花费c[2]的代价,连接两个星球,或者c[3]连接3个星球以此类推,求最终使得总的n个星球都是互相连通的最小代价。

分析:其实能够读懂上面的题意就已经基本完成了,利用贪心的思想,肯定是想让公司免费修k个最贵的,所以我们需要的是花费代价连接便宜的边,有两种方式,一个是用c[i]来连接,一个是用距离平方连接,下一个问题就是有几条边用c[]来连,几条边手动连,这个是不好判断的,所以就初始先令答案为c[n-k],所有剩下的都让公司来付费连,再贪心每次加入最小生成树最小边,把最小边用距离平方连,剩下边用c[]连,两种方式取最小值,直到最小生成树中前n-k条小的边全部连完之后的ans即为最终答案。

PS:具体如何最小生成树应该大家都会吧,这里相当于是一个完全图,边权就是两个顶点的距离平方建图,我用的是克鲁斯卡尔方法。还有这题如果宏定义全开Longlong会t,只要可能会超的开long long就好。

代码如下:

//#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
using namespace std;

const int maxn=3e3+10;
const int maxm=maxn*maxn/2;
int x[maxn],y[maxn],z[maxn],c[maxn],fa[maxn],siz[maxn];
int read(){
    int x=0,f=0,ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return f?-x:x;
}
int Find(int x){return fa[x]==x?fa[x]:fa[x]=Find(fa[x]);}
ll dis(int i,int j){
    return (x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j])+(z[i]-z[j])*(z[i]-z[j]);
}
struct Node{
    int from,to;
    ll w;
};
bool cmp(Node a,Node b){return a.w<b.w;}
vector<Node>v;
signed main(){
    int n=read(),k=read();
    for(int i=1;i<=n-k;i++)
        c[i]=read();
    for(int i=1;i<=n;i++)
        x[i]=read(),y[i]=read(),z[i]=read(),fa[i]=i,siz[i]=1;
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
            v.push_back(Node{i,j,dis(i,j)});
    sort(v.begin(),v.end(),cmp);
    int m=n-k;
    int tem=0;
    ll sum=0;
    ll ans=c[m];
    for(int i=0;i<v.size()&&tem<m;i++){
        int a=v[i].from,b=v[i].to;
        int faa=Find(a),fab=Find(b);
        if(siz[faa]>siz[fab]) swap(faa,fab);
        if(faa==fab) continue;
        tem++;
        sum+=v[i].w;
        ans=min(ans,sum+c[m-tem]);
        fa[faa]=fab;
        siz[fab]+=siz[faa];
    }
    ans=min(ans,(ll)c[m]);
    cout<<ans<<endl;
    //system("pause");
    return 0;
}

原地传送

题面:

有一个 n×m 的网格图,从(x,y) 可以走到 (x+1,y) 或者 (x,y+1) 。

地图上有k 个传送门,一旦你走到某一个传送门就会立即触发传送,你需要选择一个目标传送门,立即移动到目标传送门的位置(传送之后不会再连续触发传送)。

你初始位于起点 (0,0) ,要到达终点(n,m) ,问你从起点出发到达终点,并且恰好传送一次的方案数。

答案对 998244353998244353 取模。

请注意,不可以 原地传送(也就是目标传送门不可以是当前所在传送门)。

分析:

如果去掉传送门这个东西,那这题就很像一个dp的模板题。不过补题的时候,我敏锐发现这题标签里有一个组合数学,就想起了当初高中学组合数时候有一类模板题,就是从(0,0)到(x,y),每次向上走一格,或者向右走一格,有几种走法。由于条件限制,只能向右或者向上,那么把走的步骤写下来,类似右右上右这样的,所以方案数等于总的右和上的个数,选择右数量个位置放右,也就是C(x+y,x)。

有了上面的基础,考虑这个传送门,因为恰好传送一次,我们可以分别计算出每个传送门对应,从出发点到传送门的方案数,和传送门到终点的方案数,O(n²)的枚举两个传送门,将这二者相乘,就是恰好经过这个传送门的方案数。

然后快速写完上面的想法后,第一个样例正确,第二个样例是错误的。为什么呢?

我自己写也只是写了上面的,然后想不出来为什么错了,看了别人的题解发现。因为题目限制,经过了传送门必须要传送,设st[i]表示从出发点到i传送门的方案数,直接用C(x[i]+y[i],x[i]),计算的方案数是包括中间路径上经过了其他的传送门,所以真正的st[i]表示的含义应该是,从出发点到第i个传送门,并且不经过其他传送门的方案数。

所以这题的难点,就是如何计算不经过其他传送门,且到达第i个传送门的方案数。我们可以考虑反面来做,先计算出总的方案数,再减去不合法的方案数。

以st[i]为例,C(x[i]+y[i],x[i])计算了从出发点到i传送门的所有方案数,考虑其中不合法,也就是经过了其他传送门的方案数,我们可以枚举j传送门,计算以j传送门作为第一个经过的重复传送门的方案数,这个的计算就等于从出发点到j的合法方案数乘上从j到i的任意走法的方案数,而前面的那个合法方案数由于正向枚举,在前面就已经算出来了就等于st[j],后面的任意方案数就用C(node[i].x+node[i].y-node[j].x-node[j].y,node[i].x-node[j].x)来表示,还是挑中间的步数选右或者上。这样去掉重复之后,得到的就是正确的st函数了,ed函数也同理,逆向枚举,减去不合法方案数。

最终的代码如下:

//#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define int long long
using namespace std;
int read(){
    int x=0,f=0,ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return f?-x:x;
}
const int maxn=1e6+10;//需要的最大组合数n,m的范围
const int maxm=2e3+10;
const int mod=998244353;
ll fac[maxn], ifac[maxn];
inline ll ksm(ll a,ll b){
    ll res=1;
    for(;b;b>>=1,a=a*a%mod){
        if(b&1) res=res*a%mod;
    }
    return res;
}
inline ll C(int n, int m){//m是小的数字,n是大的
    if (n < m || m < 0)return 0;
    return 1ll*fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}

inline ll A(int n, int m){//m是小的数字,n是大的
    if (n < m || m < 0)return 0;
    return fac[n] * ifac[n - m] % mod;
}
void init(){
    fac[0] = 1;//阶乘预处理
    for (int i = 1; i <= maxn - 5; i++)
        fac[i] = fac[i - 1] * i % mod;
    ifac[maxn - 5] = ksm(fac[maxn - 5], mod - 2);
    for (int i = maxn - 5; i; i--)
        ifac[i - 1] = ifac[i] * i % mod;
}
int st[maxm],ed[maxm];
int n,m,k;
struct Node{
    int x,y;
}node[maxm];
bool cmp(Node a,Node b){
    if(a.x!=b.x) return a.x<b.x;
    else return a.y<b.y;
}
signed main(){
    init();
    n=read(),m=read(),k=read();
    for(int i=1;i<=k;i++)
        node[i].x=read(),node[i].y=read();
    sort(node+1,node+1+k,cmp);
    for(int i=1;i<=k;i++){
        st[i]=C(node[i].x+node[i].y,node[i].x);
        for(int j=1;j<i;j++){
            if(node[j].y<=node[i].y)
                st[i]=(st[i]-st[j]*C(node[i].x+node[i].y-node[j].x-node[j].y,node[i].x-node[j].x)%mod+mod)%mod;
        }
    }
    for(int i=k;i;i--){
        ed[i]=C(n-node[i].x+m-node[i].y,n-node[i].x);
        for(int j=k;j>i;j--){
            if(node[j].y>=node[i].y)
                ed[i]=(ed[i]-ed[j]*C(node[j].x+node[j].y-node[i].x-node[i].y,node[j].x-node[i].x)%mod+mod)%mod;
        }
    }
    ll ans=0;
    for(int i=1;i<=k;i++)
        for(int j=1;j<=k;j++)
            if(i!=j) ans=(ans+st[i]*ed[j]%mod)%mod;
    cout<<ans<<endl;
    //system("pause");
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值