前言:
新的百度之星就要开始了,所以来补一下往年题,并分享一些刚刚补好的题的题解,也祝愿大家能够取得一个好成绩。
星球联通
题目:有 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;
}