2020 KAIST 10th ICPC Mock Contest部分题目解答
题目链接 https://codeforces.com/gym/102760
B
首先计算出 Donghyun
可以抗 \(t=\lceil \frac{c}{5} \rceil\) 次伤害。
如果 t>b
,那就死不了,存活概率 \(1\) 。
如果 t<=b
,总情况有 \(C_a^b\) 种, 而保证 Donghyun
死掉的情况有 \(C_{a-b}^{b-t}\) 种,故存活的概率为
代码:
#pragma GCC optimize("O3")
#include<bits/stdc++.h>
using namespace std;
int a,b,c;
int main(){
cin>>a>>b>>c;
int t=(c%5==0?c/5:c/5+1);
if(t>b) cout<<1<<endl;
else{
long double ans=1;
for(int i=1;i<=t;i++) ans=ans*(b-i+1)/(a-i+1);
cout<<setprecision(10)<<1-ans<<endl;
}
return 0;
}
D
记所求的值分别为 minv,maxv
,前者很好求,就是对边排序,最小生成树就是用最短的 n-1
条边得到的,所得权值显然最小。
但是后者并不能取最长的 n-1
条边,因为对应的最小生成树无法保证是用它们连成的。
那么大致的思想是让比较小的边尽量组成完全图,这样它们就不足以得到生成树。
具体来讲就是对边进行排序,(假设给了一个 \(n\) 阶完全图)然后先让小边组成 \(n-1\) 阶完全图,这样大边就可以选出最小的那条去得到生成树。
这样一来,我们就可以按照这个策略下去,让小边按顺序组成 \(1,2,3...n-1,n\) 阶完全图。(可以用反证法证明没有更好的策略了。)
那么生成树权值和就是由区间[1,1],[2,3],[3,6],...[(n-1)*(n-2)/2+1,n*(n-1)/2]
中的最小值求和得来。
#pragma GCC optimize("O3")
#include<bits/stdc++.h>
using namespace std;
#define SET0(a) memset(a,0,sizeof(a))
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define DWN(i,a,b) for(int i=(a);i>=(b);i--)
#define INF 0x3f3f3f3f
typedef long long ll;
const int N=100,M=N*N;
int n;
int a[M];
int main(){
cin>>n;
FOR(i,1,n*(n-1)/2) cin>>a[i];
sort(a+1,a+1+n*(n-1)/2);
ll minv=0,maxv=0;
FOR(i,1,n-1) minv+=a[i];
int s=1;
FOR(i,1,n-1)
{
s+=i-1;
maxv+=a[s];
}
cout<<minv<<' '<<maxv<<endl;
return 0;
}
F
这题一眼看过去就是二分答案,枚举起点,然后二分求出相应起点对应正方形的边长最大值,不断更新就好了。
在二分check
的时候需要知道区间的最小值,故用ST表
维护。
总的时间复杂度是 \(O(N\log{N})\)
好像还可以用单调栈
来做,时间复杂度是 \(O(N)\),但我不熟悉太弱了。
#pragma GCC optimize("O3")
#include<bits/stdc++.h>
using namespace std;
#define SET0(a) memset(a,0,sizeof(a))
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define DWN(i,a,b) for(int i=(a);i>=(b);i--)
#define INF 0x3f3f3f3f
typedef long long ll;
const int N=3e5+5,M=20;
int a[N];
int n;
//st
int st[N][M];
int Log[N];
void pre(){
Log[1]=0;
FOR(i,2,N-1) Log[i]=Log[i/2]+1;
}
void init(){
FOR(j,0,M-1)
for(int i=1;i+(1<<j)-1<=n;i++)
if(!j) st[i][j]=a[i];
else st[i][j]=min(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
int query(int l,int r){
int s=Log[r-l+1];
return min(st[l][s],st[r-(1<<s)+1][s]);
}
//binary checker
bool check(int cur,int len){
return len<=query(cur,cur+len-1) && cur+len-1<=n;
}
int main(){
cin>>n;
FOR(i,1,n) cin>>a[i];
//build st
pre();
init();
int ans=1;
FOR(i,1,n){
int l=0,r=n;
while(l<r){
int mid=l+r>>1;
if(check(i,mid+1)) l=mid+1;
else r=mid;
}
ans=max(ans,l);
}
cout<<ans<<endl;
return 0;
}
H
一开始我还读错题意了,以为是贪心,事实上不是orz
,这题就是一个暴力,枚举 六种物品
取与不取即可,共 \(2^6\) 种策略。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int v[7],a[N];
int ans=0;
int n,k;
bool choose[64][7];
// 记录策略
int main(){
cin>>n>>k;
for(int i=1;i<=6;i++) cin>>v[i];
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=0;i<(1<<6);i++){
for(int j=1;j<=6;j++){
choose[i][j]= (i>>j-1)&1;
}
}
for(int i=0;i<(1<<6);i++){
int t=k;
int rec=0;
for(int j=1;j<=n;j++){
if(choose[i][a[j]] && t>=v[a[j]]){
// 模拟能否选取,如果能则必须选。
t-=v[a[j]];
rec++;
}
}
ans=max(ans,rec);
}
cout<<ans<<endl;
return 0;
}
K
构造题,可以先放在一维上思考,此时的策略是 \({1,2,3...,n-1,n,n-1,...3,2,1}\)
推广到平面:
想到将 \(x坐标\) 为第一关键字, \(y坐标\) 为第二关键字排序,尽量化为一条线处理,此时类似地连边即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n;
struct node{
int x,y;
int id;
}p[N];
bool cmp(node a,node b){
if(a.x<b.x) return true;
if(a.x==b.x) return a.y<b.y;
return false;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>p[i].x>>p[i].y;
p[i].id=i;
}
sort(p+1,p+1+n,cmp);
cout<<2*n-1<<endl;
for(int i=1;i<=n;i++) cout<<p[i].id<<' ';
for(int i=n-1;i;i--) cout<<p[i].id<<' ';
cout<<endl;
return 0;
}