SF Round 3
SF Round 3
$$\lceil \text{SF Round 3} \rfloor$$
赛后总结
经过 $8$ 个小时的比赛,$\text{SF Round 3}$ 落下帷幕。
本场比赛共有 $36$ 人参加。在 $\text{SF Round}$ 的历史上,这是第一次有除昆明以外的地州选手参赛,而他们也展现了不俗的实力。可喜可贺!
本场比赛共有 $4$ 人 AK,$16$ 人取得有效分数。
本场比赛贴近 NOIP 出题,在考查知识点上基本完备。但仍然有许多不足之处:区分度不够、部分分与子任务设置不佳、数学结论题争议较大等。这将会是我们在筹备之后比赛的宝贵经验,一定将予以改进。
题解
A. 灵感与推敲(poem)
注意到对于不同的 $n$,$s$ 显然满足单调性,故二分答案即可。
注意 $s$ 随 $n$ 的增大而减小,以及二分时对 $\texttt{min}$ 和 $\texttt{max}$ 的不同处理方法。
核心代码如下:
int t,s;
long long min,max;
int x[N];
namespace WalkerV {
void Read() {
scanf("%d%d",&t,&s);
for(int i=1;i<=t;i++) {
scanf("%d",&x[i]);
}
return;
}
long long Check(long long n) {
long long ret=0,sum=0;
for(int i=1;i<=t;i++) {
sum+=x[i];
if(sum<0) {
sum=0;
}
if(sum>=n) {
ret++;
sum=0;
}
}
return ret;
}
long long LowerBoundMin(long long l,long long r) {
long long ret=INF,mid;
while(r-l>=2) {
mid=(l+r)>>1;
if(Check(mid)<=s) {
ret=mid;
r=mid;
}
else {
l=mid;
}
}
return ret;
}
long long LowerBoundMax(long long l,long long r) {
long long ret=0,mid;
while(r-l>=2) {
mid=(l+r)>>1;
if(Check(mid)<s) {
ret=mid;
r=mid;
}
else {
l=mid;
}
}
return ret;
}
void Solve() {
min=LowerBoundMin(0,INF);
max=LowerBoundMax(0,INF)-1;
return;
}
void Print() {
if(min>max) {
printf("-1\n");
}
else {
printf("%lld %lld\n",min,max);
}
return;
}
}
B. 钢条(stick)
不难发现本题的答案与 $n$ 无关。在一条直线上切似乎难以处理,可以把直线接成一个圆,多切一下,即在圆上随机选 $k+1$ 个点,把圆周切成 $k+1$ 段。根据对称性,两个问题的答案相同。
接下来我们来处理这个转化出的新问题。原问题中不能组成多边形的概率就是其中一段至少跨越了半个圆周的概率。设这段从点 $P$ 开始逆时针跨越了半个圆周,则其他所有点都在这半个圆周之外。
那么除了点 $P$ 之外,其他每个点位于最长这段的概率均为 $\frac{1}{2}$,因此总概率为 $\frac{1}{2^k}$。点 $P$ 的取法有 $k+1$ 种,因此原问题不能组成多边形的概率为 $\frac{k+1}{2^k}$,则能组成多边形的概率为 $1-\frac{k+1}{2^k}$,即 $\frac {2^{k}-k+1} {2^k}$,注意约分。
核心代码如下:
int n,k,x,y;
namespace WalkerV {
void Read() {
scanf("%d%d",&n,&k);
return;
}
void Solve() {
x=std::pow(2,k)-(k+1),y=std::pow(2,k);
while(x%2==0) {
x/=2,y/=2;
}
return;
}
void Print() {
printf("%d/%d\n",x,y);
return;
}
}
C. 收作业(homework)
不难看出,贪心是错误的。这题的数据也造的很良心,除了第一个点这种规模小到没法卡的数据之外,成功卡掉了所有贪心的做法。
显然,我们只用考虑每一列的最前面一个人(简记为前端点 $l$)和最后面一个人(简记为后端点 $r$)。因为收完这一列后马上向右走到下一列和收完这一列后先走到下一列端点所在行,再向右走到下一列是等价的,我们就按第一种情况来考虑。
那么,我们用 $f_{i,0}$ 表示走到第 $i$ 列的前端点所需的最小路程,$f_{i,1}$ 表示走到第 $i$ 列的后端点所需的最小路程。
故初始有:$f_{1,0}=l_1-1,f_{1,1}=r_1-1$。
考虑 $f_{i,0}$,只能从 $f_{i-1,0}$ 或者 $f_{i-1,1}$ 转移而来。那么就能得到转移方程:
$$f_{i,0}= \min (f_{i-1,0}+|l_{i-1}-r_i|+(r_i-l_i),f_{i-1,1}+|r_{i-1}-r_i|+(r_i-l_i))+1$$
同理,对 $f_{i,1}$, 有转移方程:
$$f_{i,1}= \min (f_{i-1,0}+|l_{i-1}-l_i|+(r_i-l_i),f_{i-1,1}+|r_{i-1}-l_i|+(r_i-l_i))+1$$
最终答案,应当是 $\min (f_{n,0}+(m-l_n),f_{n,1}+(m-r_n))$。
核心代码如下:
int n,m,ans;
int s[N],a[N][N],l[N],r[N],f[N][2]; //0:l 1:r
namespace WalkerV {
void Read() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {
l[i]=n+1,r[i]=0;
scanf("%d",&s[i]);
for(int j=1;j<=s[i];j++) {
scanf("%d",&a[i][j]);
l[i]=std::min(l[i],a[i][j]);
r[i]=std::max(r[i],a[i][j]);
}
}
return;
}
void Solve() {
f[1][0]=(r[1]-1)+(r[1]-l[1]),f[1][1]=r[1]-1;
for(int i=2;i<=n;i++) {
f[i][0]=std::min(f[i-1][0]+1+std::abs(l[i-1]-r[i])+(r[i]-l[i]),f[i-1][1]+1+std::abs(r[i-1]-r[i])+(r[i]-l[i]));
f[i][1]=std::min(f[i-1][0]+1+std::abs(l[i-1]-l[i])+(r[i]-l[i]),f[i-1][1]+1+std::abs(r[i-1]-l[i])+(r[i]-l[i]));
}
ans=std::min(f[n][0]+(m-l[n]),f[n][1]+(m-r[n]));
return;
}
void Print() {
printf("%d\n",ans);
return;
}
}
D. 中国高铁(railway)
当 $k=0$ 时,这题是标准的单源最短路。
当 $k>0$ 时,我们用分层图来解决它。
在未修建任意一条高铁前,你只需关注最上面一层图(第 $0$ 层图)。在修建第 $i$ 条高铁后,你就将第 $i-1$ 层图中的高铁起点与第 $i$ 层图中的高铁终点相连,边权为原边权的一半。如此,最多有 $k+1$ 张平行的图。
因为上下平行的各层图都是相同的,所以这并不影响单源最短路的正确性。因为题目不要求要把 $k$ 条高铁都修建完,所以取各层图中 $dis_{1,n}$ 的最小值即可。
核心代码如下:
#define N 60
#define M 75010
#define INF 0x7FFFFFFF
int n,m,k,cnt,ans;
int head[N],dis[N][N];
bool vis[N];
struct node {
int stage,d,num;
bool operator < (const node &rhs) const {
return rhs.num<num;
}
};
struct edge {
int nxt,to,w;
};
edge e[M];
namespace WalkerV {
void AddEdge(int u,int v,int w) {
e[++cnt]=(edge){head[u],v,w};
head[u]=cnt;
}
void Read() {
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
AddEdge(u,v,w),AddEdge(v,u,w);
}
return;
}
void Dijkstra() {
dis[1][0]=0;
std::priority_queue <node> q;
node p=(node){0,0,1};
q.push(p);
while(q.size()>0) {
node t=q.top();
q.pop();
for(int i=head[t.num];i;i=e[i].nxt) {
if(dis[e[i].to][t.stage]>t.d+e[i].w) {
dis[e[i].to][t.stage]=t.d+e[i].w;
node p=(node){t.stage,dis[e[i].to][t.stage],e[i].to};
q.push(p);
}
if(t.stage+1<=k) {
if(t.d+e[i].w/2<dis[e[i].to][t.stage+1]) {
dis[e[i].to][t.stage+1]=t.d+e[i].w/2;
node p=(node){t.stage+1,dis[e[i].to][t.stage+1],e[i].to};
q.push(p);
}
}
}
}
return;
}
void Solve() {
for(int i=1;i<=n;i++) {
for(int j=0;j<=k;j++) {
dis[i][j]=INF;
}
}
Dijkstra();
ans=INF;
for(int i=0;i<=k;i++) {
ans=std::min(ans,dis[n][i]);
}
return;
}
void Print() {
printf("%d\n",ans);
return;
}
}