预期 | 一测 | ||
a | 100 | 100 | 0 |
b | 100 | 60 | -40 |
c | 100 | 80 | -20 |
d | 100 | 100 | 0 |
e | 100 | 100 | 0 |
f | 0 | 0 | 0 |
g | 100 | 0 | -100 |
h | 100 | 100 | 0 |
总分 | 700 | 540 | -160 |
问题 A: 医院设置
题意为在一棵树里找一个根,使得每个点到根的距离乘上点权的和最小,用dep数组表示距离,对于每一个点,都把他令为根然后从这个根dfs一遍求出所有可能的答案值就行了
int dfs(int x,int dep){
v[x]=1;
int ret=0;
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
if(!v[y]) ret+=dfs(y,dep+1);
}
ret+=dep*p[x];
return ret;
}
问题 B: 黑暗城堡
Dijkstra(记得用领接矩阵)跑出最短路后,用一个集合记录现已加入城堡的点有哪些,1 是已在集合中,再将刚才的代码复制一遍仿照Dijkstra, 2~n 枚举当前不在集合中的最小的dist[t](也可以理解为排序,从最小的dist开始依次加入集合中,若用前向星/领接表[排序操作]就会很难,代码也更丑,而n<=1000的数据使得完全可过),然后找到已在集合中且满足
的 j 的个数 k 然后根据乘法原理把每一步的 k 累乘起来即为答案
int main() {
scanf("%d%d",&n,&m);
memset(a,0x3f,sizeof(a));
for (int i=1;i<=n;i++) a[i][i]=0;
for (int i=1;i<=m;i++) {
int x,y,z;
scanf("%d %d %d",&x,&y,&z);
a[x][y]=a[y][x]=min(a[x][y],z);
}
memset(d,0x3f,sizeof(d));
d[1]=0;
for(int i=1;i<n;i++) {
int t=0;
for(int j=1;j<=n;j++)
if (!v[j] && (!t || d[j]<d[t])) t=j;
v[t]=1;
for(int j=1;j<=n;j++)
d[j]=min(d[j],d[t]+a[t][j]);
}
memset(v,0,sizeof(v));//v的回收利用
v[1]=1;
ll ans=1;
for (int i=1;i<n;i++) {//这不就是复制粘贴吗
int t=0,k=0;
for (int j=2;j<=n;j++)
if(!v[j] && (!t || d[j]<d[t])) t=j;
for (int j=1;j<=n;j++)
if(v[j] && d[j]+a[j][t]==d[t]) k++;
v[t]=1;
(ans*=k)%=P;
}
printf("%d",ans);
return 0;
}
问题 C: 繁忙的都市(city)
最小生成树模板,在用Kruskal算法求最小生成树是将每一条加入树的边取最大值就行了,因为一棵最小生成树必定包含无向图中最小的那些边。
#include<bits/stdc++.h>
using namespace std;
struct E{
int x,y,z;
}e[6010];
bool operator < (E x,E y){return x.z<y.z;}
int fa[6010];
int get(int x){return fa[x]==x ? x : fa[x]=get(fa[x]);}
int n,m,ans;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].z);
sort(e+1,e+m+1);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++){
int x=e[i].x,y=e[i].y,z=e[i].z;
x=get(x),y=get(y);
if(x==y) continue;
ans=max(ans,z);
fa[x]=y;
}
cout<<n-1<<" "<<ans;
}
问题 D: 走廊泼水节
在Kruskal算法求最小生成树的过程中,对于将要合并的两个点x,y所在的集合和来说因为要保证最后的最小生成树不变,那两个集合需要新建的边都至少要大于x和y之间的边。所以只需把答案加上,z为x和y之间的边的边权,|S|表示该集合的大小(点的数量)。
#include<bits/stdc++.h>
using namespace std;
struct E{
int x,y,z;
}e[6010];
bool operator < (E x,E y){return x.z<y.z;}
int fa[6010],s[6010];
int get(int x){return fa[x]==x ? x : fa[x]=get(fa[x]);}
int T,n;
long long ans;
int main(){
cin>>T;
while(T--){
scanf("%d",&n);
for(int i=1;i<n;i++)scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].z);
sort(e+1,e+n);
for(int i=1;i<=n;i++){
fa[i]=i;
s[i]=1;
}
for(int i=1;i<n;i++){
int x=e[i].x,y=e[i].y,z=e[i].z;
x=get(x),y=get(y);
if(x==y) continue;
ans+=(long long)(z+1)*(long long)(s[y]*s[x]-1);
fa[x]=y;
s[y]+=s[x];
}
cout<<ans<<endl;
ans=0;
}
}
问题 E: 最优贸易
先以 1 为起点,SPFA一遍求出一个数组 D ,表示表示从节点 1 到 当前节点的路径中,能经过的最小点权值,由于不满足Dijkstra的贪心性质,需要选择SPFA算法,同理再以 n 为起点,求最大值数组 F 然后遍历所有的 F-D 就行了。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+6,M=1e6+6;
int n,m,tot,val[N],ans;
int h[N],to[M],nxt[M],d[N];
int fh[N],fto[M],fnxt[M],f[N];
priority_queue<pair<int,int> >q,fq;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",val+i);
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
to[++tot]=y;nxt[tot]=h[x];h[x]=tot;
fto[tot]=x;fnxt[tot]=fh[y];fh[y]=tot;
if(z==2){
to[++tot]=x;nxt[tot]=h[y];h[y]=tot;
fto[tot]=y;fnxt[tot]=fh[x];fh[x]=tot;
}
}
memset(d,0x3f,sizeof(d));
d[1]=val[1];
q.push(make_pair(-d[1],1));
while(q.size()){
int x=q.top().second;
q.pop();
for(int i=h[x];i;i=nxt[i]){
int y=to[i];
if(d[y]>d[x]){
d[y]=d[x];
d[y]=min(d[y],val[y]);
q.push(make_pair(-d[y],y));
}
}
}
memset(f,0xcf,sizeof(f));
f[n]=val[n];
fq.push(make_pair(f[n],n));
while(fq.size()){
int x=fq.top().second;
fq.pop();
for(int i=fh[x];i;i=fnxt[i]){
int y=fto[i];
if(f[y]<f[x]){
f[y]=f[x];
f[y]=max(f[y],val[y]);
fq.push(make_pair(f[y],y));
}
}
}
for(int i=1;i<=n;i++) ans=max(ans,f[i]-d[i]);
cout<<ans;
return 0;
}
问题 F: 奖金
对于每一个 a,b 建一条从 b 到 a 的单向边,并记录 a 的儿子数加一,然后将所有没有儿子的点放入队列,从队首儿子将每个爸爸的儿子数减一,当某个点 x 的儿子数变为零,不仅意味 x 的add 可以更新而且只需由这一个儿子更新,因为既然是最后一个儿子,那么这个儿子的后代的数量一定比 x 的其他儿子更大,才会使这个儿子为最后一个,因为儿子数都是一个一个减的,越往后,儿子数一定更多,就越往后才会成为 x 的最后一个儿子
#include<bits/stdc++.h>
using namespace std;
#define N 10010
int head[N],nxt[N*2],to[N*2],len[N*2],cnt;
void add(int x,int y){
to[++cnt]=y;
nxt[cnt]=head[x];
head[x]=cnt;
}
int son[N],ad[N],tot;
int n,m;
queue<int> q;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
add(y,x);
son[x]++;
}
for(int x=1;x<=n;x++)
if(son[x]==0)
q.push(x);
tot=n;
while(!q.empty()){
int x=q.front();q.pop();
tot--;
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
son[y]--;
if(!son[y]){
ad[y]=max(ad[y],ad[x]+1);
q.push(y);
}
}
}
if(tot){
puts("Poor Xed");
return 0;
}
int ans=100*n;
for(int i=1;i<=n;i++)
ans+=ad[i];
cout<<ans;
}
问题 G: 对称二叉树(tree_c)
由题意得一个编号为偶数的点必是一个左儿子,他的编号加一必是同一个爸爸的右儿子,所以只需对于每一个左儿子判断它的兄弟与它是否同真同假就行了。
int main(){
scanf("%s",c);
n=strlen(c);
for(int i=0;i<n;i++){
a[i+1]=1;
if(c[i]=='#') a[i+1]=0;
}
for(int i=2;i<=n;i++){
if((i&1)==0){
if(a[i]^a[i^1]){
puts("No");
return 0;
}
}
}
puts("Yes");
return 0;
}
问题 H: 小球(drop)
由于数据太小一个球一个球模拟都可以,又因为是满二叉树,所以一个节点 i 的左儿子就是 i*2 右儿子就是i*2+1;
scanf("%d%d",&n,&k);
n--;
n=1<<n;
int f,s;
for(int i=1;i<=k;i++){
f=1;
while(1){
if(f>=n) break;
s=f<<1;
if(a[f]) s|=1;
a[f]^=1;
f=s;
}
}
cout<<f;