XXI Open Cup named after E.V. Pankratiev. Grand Prix of Samara

CF

最后20min过了两题代码量超小的题。。。

B. Fakes and Shidget

由题意,我们要求的是,在每一对 $(a,b),(c,d)$ 选择一个,求 $\max{\frac{\sum_{i=1}^n b_i}{\sum_{i=1}^na_i}}$。

直接01分数规划即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const int N=2e5+5;
int n,a[N],b[N],c[N],d[N];
int main()
{
read(n);
fo(i,1,n)
read(a[i],b[i],c[i],d[i]);
db l=0,r=1e9,mid;
fo(i,1,100)
{
mid=(l+r)/2.;
db ans=0;
fo(i,1,n)
ans+=max(b[i]-a[i]*mid,d[i]-c[i]*mid);
if(ans>=0) l=mid;
else r=mid;
}
printf("%.10lf",mid);
return 0;
}

C. Cyclically Shifted Maze

meaningiess直接把这题秒了。。

D. Two Pirates - 2

清醒的人一定先拿走最大的那个。然后就不知道怎么做了。

可以发现,排好序后,选到某一堆的概率与其本身无关,因此可以使用DP。

设 $f_{n,i}$ 表示 $n$ 堆的时候,从小到大排名第 $i$ 被选到的概率。

分两种情况讨论即可。时间复杂度 $O(n^2)$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const int N=5005;
int n;
int a[N];
db f[N][N];
int main()
{
read(n);
fo(i,1,n) read(a[i]);
sort(a+1,a+n+1);
fo(i,1,n)
{
int opt=(n-i+1)&1;
if(opt)
{
fo(j,1,i)
f[i][j]=f[i-1][j];
f[i][i]=1;
}
else
{
fo(j,1,i)
f[i][j]=(f[i-1][j-1]*(j-1)+f[i-1][j]*(i-j))/i;
}
}
db sum=0,ans=0;
fo(i,1,n) sum+=a[i];
fo(i,1,n) ans+=f[n][i]*a[i];
printf("%.12lf %.12lf\n",ans,sum-ans);
return 0;
}

E. Powerless Mage

F. Exactly One Point

假设我们从后往前选择点。

若选择了点 $i$,则该点必须覆盖至少一条线段。设 $mx_i$ 表示所有 $l\le i$ 的线段的 $r$ 的最大值;$mn_i$ 表示所有 $l\geq i$ 的线段的 $r$ 的最小值。那么后面点的合法范围是 $[mx_i+1,mn_{i+1}]$。那么在里面随便选一个就可以了。

时间复杂度 $O(n\log n)$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const int N=1e6+5;
const int inf=1e9;
int mi[N],mx[N],l[N],r[N],n,is[N];
int fa[N];
int main()
{
read(n);
int m=n*4;
fo(i,0,m) mi[i]=inf;
fo(i,1,n)
{
read(l[i],r[i]);
l[i]++; r[i]++;
is[l[i]]++; is[r[i]+1]--;
mx[l[i]]=max(mx[l[i]],r[i]);
mi[l[i]]=min(mi[l[i]],r[i]);
}
fo(i,1,m) mx[i]=max(mx[i],mx[i-1]),is[i]+=is[i-1];
fd(i,m-1,1) mi[i]=min(mi[i],mi[i+1]);
VI ans;
set<int> s;
s.insert(m);
fd(i,m-1,1)
if(mx[i]+1<=mi[i+1])
{
auto it=s.upper_bound(mx[i]);
if(it!=s.end()&&(*it)<=mi[i+1])
fa[i]=*it,s.insert(i);
}
if(s.size()<=1) return puts("-1")&0;
if(*s.begin()>mi[1]) return puts("-1")&0;
int u=*s.begin();
for(;u;u=fa[u]) if(is[u]) ans.pb(u);
printf("%d\n",ans.size());
for(int x:ans) printf("%d ",x-1);
return 0;
}

G. Lexicographically Minimal Subsequence

贪心一个一个往后看是否能选。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const int N=1e6+5;
int n,k;
char s[N];
int now[26];
vector<int> vec[26];
int main()
{
scanf("%s\n%d",s+1,&k);
n=strlen(s+1);
fo(i,1,n) vec[s[i]-'a'].pb(i);
fo(i,1,k)
{
int pos;
fo(j,0,25)
if(now[j]<vec[j].size())
{
if(n-vec[j][now[j]]>=k-i)
{
putchar(j+'a');
pos=vec[j][now[j]];
break;
}
}
fo(j,0,25)
for(;now[j]<vec[j].size()&&vec[j][now[j]]<=pos;now[j]++);
}

return 0;
}

H. Video Reviews - 2

考虑最后一个人 $a_n$,如果 $a_n<m$,那么一定不选。转换为 $(n-1,m-1)$ 的子问题。

如果 $a_n\geq m$,且 $n=m$,则这个一定会被选;如果 $n>m$,则这个可能被选,但实际上选前面的一定不会更差,因此这个一定不会选。
那么就可以直接从 $a_n$ 开始倒退至 $a_1$ 即可。

由于 $z_j$ 有逆元,同一段内可以由 $a_{i+1}$ 直接推出 $a_i$,不同段的边界处需要预处理顺推一遍进行计算。

时间复杂度 $O(n)$,空间复杂度 $O(m)$。

J. Lost Island

第三次见到这种题了。

我们记一个颜色的状态为 $(a_i,b_i)$。设某个人的颜色为 $i$。

若 $a_i=b_i>0$,则这人看到外面只有 $b_{i}-1$ 个,不合理。因此这个知道自己就是 $i$,将在第 $1$ 天自杀。

若 $a_i=b_i+1$,则这人假设自己不是 $i$,那么外面这些颜色 $i$ 的人看到的状态应为 $(a_i-1,b_i)$,将在第 $1$ 天自杀,但他们并没有,于是这人在第 $2$ 天知道自己是颜色 $i$。

以此类推,在 $x+1$ 天,某人假设自己不是 $i$,那么外面的人将在第 $x$ 天自杀,但是并没有,于是这人在 $x+1$ 这天自杀。

因此,对于 $b_i>0$,颜色为 $i$ 的人将在 $a_i-b_i+1$ 天自杀。

  • 若不存在 $b_i=0$,则 $a_i-b_i+1$ 最大的那些人将在次大的 $a_j-b_j+1$ 之后一天自杀,因为其他人都死光了。
  • 若只存在一个 $b_i=0$,则 $i$ 颜色的人在 $a_i-b_i+1$ 最大的下一天自杀。
  • 若存在两个或以上 $b_i=0$,则所有 $b_i=0$ 的人将活下来。

时间复杂度 $O(n)$。

K. Bloodseeker

首先我们把这些怪分为两类:$h_i\geq t_i$ 和 $h_i<t_i$。

第一类是可以帮助我们回血的,第二类是要我们掉血的。

显然可以在恰当时间去打第一类从而充分运用其回血,一共能回 $\sum h_i-t_i$ 滴血。

对于第二类,假设有两个怪 $(h_i,t_i),(h_j,t_j)$,当且有 $k$ 滴血量。

先打 $i$ 需要满足: $m+h_i-t_i\geq t_j$。

先打 $j$ 需要满足:$m+h_j-t_j\geq t_i$。

其他不等式一样。因此我们直接按照 $h$ 从打到小依次消掉即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct node{ll x,y;};
inline int solve()
{
ll n,m,x,y;
ll now=0;
read(n,m);
vector<node> a;
fo(i,1,n)
{
read(x,y);
y=min(y,m);
if(x<=y) now+=y-x;
else a.pb({y,x-y});
}
now+=m;
sort(all(a),[&](auto x,auto y){return x.x>y.x;});
for(auto [x,y]:a)
{
if(x+y>now) return puts("NO")&0;
else now-=y;
}
puts("YES");
return 0;
}
int main()
{
CASET solve();
return 0;
}

L. Not the Longest Increasing Subsequence

值域也为 $k$,那么直接DP一下就好了。输出方案可以倒着推一遍。

M. Binary Search Tree

树形DP一波即可。opentrains卡我常数,使用 $O(n)$ 的方法才过了。