UPC 2020年夏混合个人训练第四十一场(全题解)

问题 A: 消失的无向图
时间限制: 1 Sec 内存限制: 128 MB

题目描述
从前有一张n个点的无向图,边权都是正整数。但现在所有的边都消失了,只留下任意两点之间的最短路。

你现在想知道,所有边的边权和至少是多少。
输入
第一行一个正整数n 。
接下来一个n×n的矩阵A ,其中Ai,j代表原来图中i到j的最短路。
保证Ai,i=0,Ai,j=Aj,i。
输出
一行一个整数,表示答案。
如果不存在任何连边方案满足所有的最短路限制,输出-1 。

样例输入
3
0 1 3
1 0 2
3 2 0

样例输出
3

提示
对于30%的数据,n≤7。
对于另外30%的数据,保证存在一种最优解,满足原图是一条链。
对于100%的数据,满足n≤300,1≤Ai,j≤1e9,(i≠j) 。

题解:
先判断无解的情况,如果存在 Ai,k+Ak,j<Ai,j,则直接输出-1.
要让边权和最小,所以不能添加不在任何最短路上的边。这也意味着如果点 u 和点 v 之间有边,那么边权一定是 Au,v 。因为如果比这个小,那么 u 和 v 之间的最短路就不是 Au,v ,如果比这个大,那么可以把经过这条边改成经过 u 和 v 之间的最短路,这样距离更短,于是这条边就不会出现在任何最短路中。
我们可以一开始把所有的点对连上边,这样问题变成了删掉一些边,使任意两点之间的最短路不变,并且要使删掉边的权值和尽量大。
用类似floyed的做法,枚举一个中间点,对于一条边 (u,v) ,如果存在异于点 u,v 的点 w 使得 Au,w+Aw,v=Au,v ,就说明存在另一条长度和 Au,v 相等的从 u 到 v 的路径,就可以删掉这条边。
时间复杂度 O(n^3) 。

#include<bits/stdc++.h>
#define ll long long 
#define MN 305
using namespace std;

inline int read()  {
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')	{
	    if(ch=='-')  f=-1;
	    ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
	    x=(x<<3)+(x<<1)+ch-'0';
		ch=getchar();
	}
	return x*f;
}

ll ans,dis[MN][MN],n;
bool usd[MN][MN];

int main()
{
	n=read();
	register int i,j,k;
	for(i=1;i<=n;i++)
	    for(j=1;j<=n;j++) 
	        dis[i][j]=read();
	for(k=1;k<=n;k++)
	  for(i=1;i<=n;i++)
		for(j=1;j<=n;j++)
		  if(dis[i][k]+dis[k][j]<dis[i][j]) return 0*puts("-1"); 
	    
	for(k=1;k<=n;k++)
	  for(i=1;i<=n;i++)
	    for(j=1;j<=n;j++){
		  if(k==i||i==j||k==j) continue;
		  if(dis[i][k]+dis[k][j]==dis[i][j]) usd[i][j]=1;
	    }
	    
	for(i=1;i<=n;i++)
	  for(j=1;j<=n;j++) 
	    ans+=(1-usd[i][j])*dis[i][j];
	    
	return 0*printf("%lld\n",ans>>1LL);
}

问题 B: 跳(jump)
时间限制: 1 Sec 内存限制: 256 MB

题目描述
Steaunk 喜欢跳。
他想跳多远就能跳多远。
他要跳往目的地。目的地和他的距离是D 。
然而他很浪,他想每次随机生成一个距离,然后朝着目的地跳过去。
当然他有可能跳过头,也就是跳到了目的地的另一侧,当然,这时候他会转向,重新朝向目的地。
Steaunk 是聪明的,所以如果他发现跳过之后他到目的地的距离比原来还远的话,他会选择不跳。
E.Space 知道Steaunk 的随机数生成器接下来按顺序产生的nnn 个数。
他想知道,对于每个1≤i≤m,他能否通过强行改变这n个数中的第qi个数,使得Steaunk 无法在n步之内到达目的地。
注意:可以改变成原来的数值。
输入
第一行两个正整数n ,D 。
第二行n个正整数ai,表示Steaunk 的随机数生成器接下来生成的第i个数是ai。
第三行一个正整数m 。
第四行m个正整数,其中第i个表示qi。
输出
输出m行。
对于第i行,如果E.Space 能通过强行改变这n个数中的第qi个数,使得Steaunk无法在n步之内到达目的地,那么输出YES ,否则输出NO

样例输入
4 10
3 4 3 3
2
4 3

样例输出
NO
YES

提示
对于30%的数据,保证n≤100,D≤2500。
对于另外20%的数据,保证ai在[1,D]之间均匀随机。
对于100%的数据,保证n≤5×1e5,m≤5×1e5,D≤1e9,ai≤1e9,qi≤n。

题解:
预处理出如果 E.Space 不做任何改变,那么每一步之后 Steaunk 会跳到哪里。记第 i 步之后 Steaunk 与目的地的距离是 di ( d0=D) 。考虑对于每个时刻求出所有使得 Steaunk 能够到达目的地的位置集合。形式化地,定义集合 Si(i=1,2,…,n+1) ,如果在第 i−1 步之后 Steaunk 与目的地的距离是 x 且之后 E.Space 不做任何改变,Steaunk 能到达目的地,那么 x∈Si 。
对于 q=i 的询问,如果 di−1≥mex Si+1 ,即存在一个小于等于 di−1 的正整数不在 Si+1 中,那么 E.Space 可以把 Steaunk 移到这个位置,使他不能到达目的地,所以答案是 YES ,否则答案是 NO
显然 Sn+1=0,mex Sn+1=1 。
设 mex Si+1=x 。
如果 ai≥2x ,显然 x∉Si 且 mex Si≥x ,所以 mex Si=x 。
如果 ai<2x ,则 ∀j∈n j∈[0,x+ai),j∈Si 且 x+ai∉Si ,所以 mex Si=x+ai 。
我们发现,询问只和 mex Si 有关,而 mex Si 的计算也只和 mex Si+1 和 ai 有关,于是我们可以直接计算 mex Si 而不用计算 Si 。
时间复杂度 O(n+m) 。

#include<bits/stdc++.h>
#define MN 500005
using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9') {
	    if(ch=='-')f=-1;
	    ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
	    x=(x<<3)+(x<<1)+ch-'0';
	    ch=getchar();
	}
	return x*f;
}

int n,m,D,a[MN],d[MN],q[MN],mex[MN];

int main()
{
	register int i,j,k;
	n=read(),D=read();
	mex[n+1]=1;
	d[0]=D;
	
	for(i=1;i<=n;i++) a[i]=read();
	m=read();
	for(i=1;i<=m;i++) q[i]=read();
	for(i=1;i<=n;i++) d[i]=min(d[i-1],abs(d[i-1]-a[i]));
	
	for(i=n;i;--i) mex[i]=((mex[i+1]<<1)>a[i])?mex[i+1]+a[i]:mex[i+1];
	for(i=1;i<=m;i++) puts(d[q[i]-1]>=mex[q[i]+1]?"YES":"NO");
	
	return 0;
}

问题 C: 动态完全图
时间限制: 1 Sec 内存限制: 128 MB

题目描述
你的目标是维护一个动态图。
开始图有 n 个顶点,边集为空。
你需要支持下列三种操作:
1.加入一条连接顶点 u 和 v 的无向边。
2.对于所有 x ,y ,如果 x 和 u 连通并且 y 和 u 连通,加入一条连接顶点 x 和 y 的无向边。
3.询问图中是否存在一条连接顶点 u 和 v 的边。
输入
第一行两个正整数 n ,m ,其中 m 表示操作次数。
接下来 m 行,每行表示一个操作。

对于操作1 ,格式为 1 u v 。
对于操作2 ,格式为 2 u 。
对于操作3 ,格式为 3 u v 。

其中对于操作1,3 ,满足 u≠v。
输出
对于每个操作3 ,输出一行,如果存在一条连接顶点 u 和 v 的边,则输出 Yes ,否则输出 No 。

样例输入
3 6
1 1 2
1 2 3
3 1 2
3 1 3
2 1
3 1 3

样例输出
Yes
No
Yes

提示
对于20%的数据,满足n,m≤100。
对于50%的数据,满足n≤1000,m≤10000。
对于另外10%的数据,满足没有操作2。
对于100%的数据,满足n≤1e5,m≤2×1e5。

题解:

#include<bits/stdc++.h>
#define MN 100005
#define ME 200005 
using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9') {
	    if(ch=='-')f=-1;
	    ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
	    x=(x<<3)+(x<<1)+ch-'0';
	    ch=getchar();
	}
	return x*f;
}

int n,m,fu,fv,opt,u,v,par[MN];
int q[MN],l,r;
bool vis[MN];
map<int,bool> mp[MN];

inline int getf(int x) {
    return par[x]==x?x:par[x]=getf(par[x]);
}

struct edge {
    int to,nex;
}e[ME<<1];

int cnt=0,hr[MN];

inline void ins(int f,int t)
{
	e[++cnt]=(edge) {t,hr[f]};  hr[f]=cnt;
	e[++cnt]=(edge) {f,hr[t]};  hr[t]=cnt; 
}

int main()
{
	n=read(),m=read();
	register int i,j,head,tail;
	for(i=1;i<=n;++i) par[i]=i;
	
	while(m--) {
		opt=read();u=read();
		switch(opt) {
		
			case 1: v=read(); mp[u][v]=mp[v][u]=true;
                     fu=getf(u); fv=getf(v); ins(fu,fv); break;
                     
			case 2: fu=getf(u); memset(vis,0,sizeof vis); 
					for(q[l=r=1]=fu,vis[fu]=true;l<=r;++l)
					for(j=hr[q[l]];j;j=e[j].nex)
						if(!vis[e[j].to]) vis[q[++r]=e[j].to]=true;
					for(j=1;j<=r;++j) hr[q[j]]=0,par[q[j]]=fu;
					break;
					
			case 3: v=read();
					if(mp[u][v]||getf(u)==getf(v)) puts("Yes");
					else puts("No");break;
		}
	}
	return 0;
}

问题 D: AP数
时间限制: 1 Sec 内存限制: 128 MB

题目描述
正整数n是无穷的,但其中有些数有神奇的性质,我们给他个名字——AP数。
对于一个数字i他是AP数的充要条件是所有比他小的数的因数个数都没有i的因数个数多。比如6的因数是1 2 3 6 共计有4个因数。他就是一个AP数(1-5的因数个数不是2就是3)。我们题目的任务就是找到一个最大的,且不超过n的AP数。

输入
每个测试点可能拥有多组数据。请用seekeof来确认是否已读完
对于每一行有一个n(n<=15 0000 0000),如题目所描述

输出
对于每一行输出最大的且不超过n的AP数

样例输入
1000

样例输出
840

题解:
1.暴力打表
2.找规律
3.试着写正解
首先打表,我当时打出了小于等于100000的所有符合条件的数,直接看也没看出来什么,然后当然就是老套路了–质因数分解,发现规律:
1.分解出来的质因数一定连续
2.质因数的幂次一定不上升
3.由(1)可得质因数最多10种
然后就可以dfs了,可以加各种剪枝:
1.可行性:小于等于n
2.最优性:约数个数大的替换小的
约数个数可以用这个算:
一边dfs一边更新就好了。

#include <bits/stdc++.h>
#define ll long long 
using namespace std;
ll n,ap,fap;
int mm[30]={2,3,5,7,11,13,17,19,23,29};

void dfs(ll num,ll fnum,ll i,ll j)
{
    if(fap<fnum||(fap==fnum&&ap>num))
	{
        fap=fnum;
        ap=num;
    }
    int t=1;
    while(t<=j&&num*mm[i]<=n)
	{
        num*=mm[i];
        dfs(num,fnum*(t+1),i+1,t);
        t++;
    }
    return;
} 

int main()
{
    while(~scanf("%lld",&n))
    {
    	ap=0;
		fap=0;
   	 	dfs(1,1,0,20);
		cout<<ap<<endl;
	}
}

问题 E: 路由器安置
时间限制: 1 Sec 内存限制: 128 MB

题目描述
一条街道安装无线网络,需要放置M个路由器。整条街道上一共有N户居民,分布在一条直线上,每一户居民必须被至少一台路由器覆盖到。现在的问题是所有路由器的覆盖半径是一样的,我们希望用覆盖半径尽可能小的路由器来完成任务,因为这样可以节省成本。
输入
第一行包含两个整数M和N,以下N行每行一个整数Hi表示该户居民在街道上相对于某个点的坐标。
输出
仅包含一个数,表示最小的覆盖半径,保留一位小数。

样例输入
2 3
1
3
10

样例输出
1.0

提示
对于100%的数据,有1≤N,M≤100000,-10000000≤Hi≤10000000。

题解:
首先这种问题可以采用二分答案的方法. 尝试当前路由器覆盖范围能否覆盖所有居民点. 那么这里如果采用二分半径的方法, 会出现实数, 所以采用 wxjlzbcd 的方法, 采用二分直径, 最后实数折半输出。
在尝试答案可行性的时候, 为了快速查找从当前居民点可以覆盖到的最远居民点, 再采用一次二分查找 (upper_bound) 找到刚好覆盖不到的一个点, 也就是下一个起点. 注意这里的几个变量的初值和边界值需要好好考虑一下。

#include<bits/stdc++.h>
using namespace std;
int n,m,l,r,mid;
int p[100010];
char B[1<<15],*S=B,*T=B;

char getchar2() {
    return S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?0:*S++;
}

int read() {
    int x=0; int f=1;
    char ch=getchar2();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar2();}
    while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+ch-'0'; ch=getchar2();}
    return x*f;
}

int main() {
    m=read();
	n=read();
    for(int i=1;i<=n;i++) p[i]=read();
    sort(p+1,p+n+1);
    for(l=0,r=p[n]-p[1],mid=(l+r)/2;l<r;mid=(l+r)/2) {
        int c=0,to=-987654321;
        for(int i=1;i<=n;i++)
            if(p[i]>to) {
                   c++;
                   to=p[i]+mid;
            }
        if(c>m) l=mid+1;
        else r=mid;
    }
    printf("%.1lf",l/2.0);
    return 0;
}

问题 F: Binary Tree Number
时间限制: 1 Sec 内存限制: 128 MB

题目描述
由n个节点可组成多少个不同的二叉树?
输入
一个正整数n。
输出
不同的二叉树的个数。

样例输入
1

样例输出
1

提示
保证40%的数据n<=35;
保证100%的数据n<=5000。

XDJM们,这卡特兰大数原谅我调不对,等待有缘人助我
贴一下大佬关于卡特兰大数的解析:卡特兰大数

问题 G: 电脑分配
时间限制: 1 Sec 内存限制: 128 MB

题目描述
学校新订购了一批 N 台电脑,想将它们分给 M 个班级。
学校想让每个班级分到尽量多的电脑,并且每个班级分到的电脑数量要一样。请问每个班级最多分到几台电脑?
输入
输入包含两个整数 N, M。代表电脑的数量和班级的数量。
对于 100% 的数据,1 <= N, M <= 1000
输出
输出包含一个数,代表一个班最多被分到的电脑数。

样例输入
8 3

样例输出
2

提示
每个班分到两台电脑之后,剩下两台就没法再分了。

题解:水题…求n对m下取整。

#include <bits/stdc++.h> 
using namespace std;

int main() {
	int n,m;
	cin>>n>>m;
	cout<<n/m; 
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

米莱虾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值