10.22集训解题报告
T1 work
100
题面:
大锤最近开了一家公司,有 \(N\) 个人来应聘,第 \(i\) 个人能够连续工作 \(a_i\) 个小时,然
后需要连续休息 $ b_i$ 个小时才能再次工作.
作为一个黑心老板,大锤想让招到的员工能够成为永动机.
具体来说,假设大锤找了 \(k\) 个人 \(w_1,\dots,w_k\) (顺序由你安排),她希望这些人能够
按照一个固定的顺序循环工作.
同时,大锤希望能够招最少的人来满足条件.但是她的数学不好,所以请你助她完成招人的工作.
思路:
如果有方案,如何判断合法呢。
对于每一个人,其他人的工作时间的总和必须大于等于这个人的休息时间,这个人才能休息好,才能续上,继续工作。
形式化的有:
不愿意看到西格玛,所以改成:
其中 \(S\) 是所有人的 \(a\) 的总和。
那么移项:
不难发现,只需满足
那么从大到小枚举 \(a+b\) ,以这个为最大值,将剩下的 \(a\) 组成的降序序列从前往后找,就能保证以最小的人数组成大于选的这个 \(a+b\) 的数,以使其合法。
那么这个“剩下的 \(a\) 组成的降序序列”,又要能维护前缀和,还能找第一个大于等于 \(a+b\) 的前缀和的位置,还能删除,复杂度还不能太高,套上个 \(FHQ\) 就行。
代码:
struct tree {
ll val, sum;
int l, r, size, ran;
}t[2000006];
struct node {
ll a, b;
}sz[500005];
int sta[500005];
bool cmp(node x, node y) {
return x.a+x.b>y.a+y.b;
}
int n, head_size, rt;
int New(int val) {
int g=++head_size;
t[g].sum=t[g].val=val;
t[g].ran=rand();
t[g].size=1;
return g;
}
void push_up(int g) {
t[g].size=t[t[g].l].size+t[t[g].r].size+1;
t[g].sum=t[t[g].l].sum+t[t[g].r].sum+t[g].val;
}
void Split(int g, int &x, int &y, int val) {
if(g==0) {
x=y=0;
return ;
}
if(t[g].val<=val) {
y=g; Split(t[g].l, x, t[y].l, val);
}
else {
x=g; Split(t[g].r, t[x].r, y, val);
}
push_up(g);
}
int Merge(int x, int y) {
if(x==0||y==0) return x+y;
if(t[x].ran<t[y].ran) {
t[x].r=Merge(t[x].r, y);
push_up(x); return x;
} else {
t[y].l=Merge(x, t[y].l);
push_up(y); return y;
}
}
int Min(ll k) {
int re=0;
int g=rt;
while(g) {
if(t[t[g].l].sum>=k) g=t[g].l;
else if(t[t[g].l].sum+t[g].val>=k) {
return re+t[t[g].l].size+1;
}
else {
k-=t[t[g].l].sum+t[g].val;
re+=t[t[g].l].size+1;
g=t[g].r;
}
}
return 1e9;
}
void build() {
for(int i=n; i>=1; --i) {
rt=Merge(rt, New(sta[i]));
}
}
signed main(){
n=read();
for(int i=1; i<=n; ++i) {
sz[i].a=read(); sz[i].b=read(); sta[i]=sz[i].a;
} sort(sta+1, sta+n+1); sort(sz+1, sz+n+1, cmp);
build();
int ans=1e9;
for(int i=1; i<=n; ++i) {
ans=min(ans, Min(sz[i].a+sz[i].b));
int T1, T2, T3;
Split(rt, T1, T2, sz[i].a);
Split(T2, T2, T3, sz[i].a-1);
T2=Merge(t[T2].l, t[T2].r);
rt=Merge(Merge(T1, T2), T3);
}
if(ans==1e9) ans=-1;
cout<<ans;
}
T2 seq
60
题面:
大锤是一个序列问题砖家,尤其擅长算公共子序列.这一天,她,改变了世界,发明了波浪公共子序列.
定义一个序列是波浪序列,当且仅当这个序列满足没有连续三个元素,满足
对于一个序列 \(a = \{a_i\},\forall{i}\in(1,k),a_i-1 \lt a_i \gt a_i+1 或者 a_i−1 \gt a_i \lt a_i=i.\)
给出两个序列 \(A,B\),求 \(A,B\) 的最长公共波浪子序列.
思路:
\(n^4暴力DP\)。
代码:
int dp[5003][5003][2];
int g[5003][2];
int a[5003], b[5003];
int ans;
int main(){
int n=read();
for(int i=1; i<=n; ++i) a[i]=read();
int m=read();
for(int j=1; j<=m; ++j) b[j]=read();
for(int i=1; i<=n; ++i) {
for(int j=1; j<=m; ++j) {
if(a[i]!=b[j]) continue ;
dp[i][j][0]=dp[i][j][1]=1;
for(int k1=1; k1<i; ++k1) {
if(a[k1]==a[i]) continue ;
for(int k2=1; k2<j; ++k2) {
if(a[k1]!=b[k2]) continue ;
if(a[k1]>a[i])
dp[i][j][0]=max(dp[i][j][0], dp[k1][k2][1]+1);
if(a[k1]<a[i])
dp[i][j][1]=max(dp[i][j][1], dp[k1][k2][0]+1);
}
}
ans=max(max(ans, dp[i][j][0]), dp[i][j][1]);
}
}
cout<<ans;
}
T3 jump
题面:
大锤有一个闺蜜,她们约定每月见一次面.但是她们都非常忙,需要非常精
确的安排才能见到面.
大锤所在的国家可以看成是数轴上的 \(N\) 个城市,依次标号成 $1,\dots, N $ 在每个城市\(i\)有一个传送器,可以将一个人传送 \([\max(1,i-a_i),\min(n,i+a_i)]\) 中的任何一个
城市,且需要花费 \(1\) 块钱.
由于她们常年奔波各地,大锤和她的闺蜜将会随机出现在这个 \(N\) 个位置中的
一个。每次见面前,两人会提前告知对方自己的位置,然后其中一个人会保持不动,另一个人用尽可能少的花费与其汇合.
现在大锤想知道,最坏情况下两个人需要花费多少钱才能够相遇。
思路:
\(n\) 遍 \(Dij\)。
代码:
int n;
struct edge {
int t, n;
}e[1000006];
int head[1003], head_size;
void ADD(int f, int t) {
e[++head_size]=(edge) {t, head[f]};
head[f]=head_size;
}
int a[100005];
int dis[1003][1003];
struct node {
int v, diss;
bool operator < (const node &a) const {
return a.diss<diss;
}
};
void Dij(int S) {
dis[S][S]=0;
priority_queue<node>q;
q.push((node){S, 0});
while(q.size()) {
node op=q.top(); q.pop();
if(op.diss!=dis[S][op.v]) continue ;
for(int i=head[op.v]; i; i=e[i].n) {
if(dis[S][op.v]+1<dis[S][e[i].t]) {
dis[S][e[i].t]=dis[S][op.v]+1;
q.push((node){e[i].t, dis[S][e[i].t]});
}
}
}
}
int main(){
n=read();
if(n==1) {
cout<<0;
return 0;
}
memset(dis, 0x3f, sizeof(dis));
for(int i=1; i<=n; ++i) {
a[i]=read();
for(int j=max(1, i-a[i]); j<=min(n, i+a[i]); ++j) {
if(i!=j) ADD(i, j);
}
}
for(int i=1; i<=n; ++i) {
Dij(i);
}
int ans=0;
for(int i=1; i<=n; ++i) {
for(int j=1; j<i; ++j) {
ans=max(ans, min(dis[i][j], dis[j][i]));
}
}
cout<<ans;
}