省选模拟测试2
总的来说,这套题比昨天的要简单了点。
实际得分: \(50+100+100 = 250\)
\(T1\) 赛时的做法在你谷吸氧能得到 \(88\) 分的好成绩,稍微改改就 \(A\) 了,你谷数据也太水了吧。
\(T2\) 稍微推一下 \(dp\) 的柿子就出来了,用线段树维护也比较容易想到。
\(T3\) 之前做过,还写过 题解,不在多说。
T1 SZA-Cloakroom
题意描述
有 \(n\) 件物品,每件物品有三个属性 \(a_i b_i, c_i (a_i<b_i)\)。
再给出 \(q\) 个询问,每个询问由非负整数 \(m, k, s\) 组成,问是否能够选出某些物品使得:
-
对于每个选的物品 \(i\) ,满足 \(a_i\leq m\) 且 \(b_i>m+s\)。
-
所有选出物品的 \(c_i\) 的和正好是 \(k\)。
如果能选出输出 \(TAK\) 否则输出 \(NIE\)
数据范围:\(n\leq 1000, a_i,b_i \leq 10^9, c_i\leq 1000,k\leq 100000\)
做法一
这道题的限制比较多,不难想到离线处理。
我们先把所有的询问都离线下来,按 \(m_i\) 从小到大排一下序,在把所有的物品按 \(a_i\) 从小到大排一遍序。
我们维护一个序列 \(q\) 表示当前所有 \(a_i\leq m\) 的物品中按 \(b_i\) 从大到小的顺序,排名为 \(i\) 的物品的编号是多少。
设 \(f[i][j]\) 表示能否在序列 \(q\) 的前 \(i\) 个物品中,选若干个物品使得其 \(c_i\) 总和为 \(j\) 。
对于每个询问,先把所有的符合条件的物品都加入到序列 \(q\) 中,然后找出第一个 \(b[q[i]] \leq m+s\) 的 \(i\), 那么显然如果 \(f[i-1][k]\) 为 \(1\) 这个询问的答案为 \(TAK\) 否则为 \(NIE\) 。
每加入一个物品都需要重新维护一下 序列 \(q[i]\) 和 \(f[i][j]\) ,但维护出 \(f[i][j]\) 的复杂度为 \(O(nk)\) 的,所以还要用 \(bitset\) 优化一下 \(f\) 的转移。
吸氧就能在你谷过掉了。
做法2(正解)
还是先把询问和物品排一下序。
设 \(f[k]\) 表示在所有 \(a_i \leq m\) 的物品中,\(c\) 属性之和为 \(k\) 的方案中 \(b[i]\) 的最小值最大是多少。
每加入一个物品,用类似于背包的转移方法就可以维护出 \(f[i]\)。
对于每一个询问,只需要判断 \(f[k]\) 和 \(m+s\) 的大小关系即可。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<bitset>
using namespace std;
const int inf = 1e9+10;
int n,m,num,f[100010],ans[1000010];
struct node
{
int a,b,c;
}e[2010];
struct Query
{
int x,y,k,id;
}q[1000010];
inline int read()
{
int s = 0, w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
bool comp(node a,node b)
{
return a.a < b.a;
}
bool cmp(Query a,Query b)
{
if(a.x == b.x) return a.y < b.y;
return a.x < b.x;
}
void add(int x)
{
for(int j = 100000; j >= e[x].c; j--)
{
f[j] = max(f[j],min(f[j-e[x].c],e[x].b));
}
}
int main()
{
n = read();
for(int i = 1; i <= n; i++)
{
e[i].c = read();
e[i].a = read();
e[i].b = read();
}
sort(e+1,e+n+1,comp);
m = read();
for(int i = 1; i <= m; i++)
{
q[i].x = read();
q[i].k = read();
q[i].y = q[i].x + read();
q[i].id = i;
}
sort(q+1,q+m+1,cmp);
int l = 1; f[0] = inf;
for(int i = 1; i <= m; i++)
{
while(l <= n && e[l].a <= q[i].x) add(l), l++;
ans[q[i].id] = f[q[i].k] > q[i].y;
}
for(int i = 1; i <= m; i++)
{
if(ans[i] == 1) printf("TAK\n");
else printf("NIE\n");
}
return 0;
}
T2 Bookshelf G
题意描述
当农夫约翰闲的没事干的时候,他喜欢坐下来看书。多年过去,他已经收集了 \(N\) \((N\leq 10^5)\)本书 他想造一个新的书架来装所有书。
每本书 \(i\) 都有宽度 \(W(i)\) 和高度 \(H(i)\)。书需要按顺序添加到一组书架上;比如说,第一层架子应该包含书籍 \(1 ... k\),第二层架子应该以第 \(k + 1\) 本书开始,以下如此。每层架子的总宽度最大为 \(L\) \((L\leq 10^9)\)。每层的高度等于该层上最高的书的高度,并且整个书架的高度是所有层的高度的总和,因为它们都垂直堆叠。
请帮助农夫约翰计算整个书架的最小可能高度。
简化题意:给你一个长度为 \(n\) 的序列 \(w[i]\),让你把这个序列分为若干段,满足每段的 \(w[i]\) 之和都小于 \(L\) ,最小化每一段的 \(h[i]\) 的最大值之和。
solution
线段树优化 \(dp\)。
首先 \(O(n^2)\) 的 \(dp\) 方法应该很容易想到。
设 \(sum[i]\) 表示 \(w[i]\) 的前缀和, \(f[i]\) 表示把前 \(i\) 个物品分成若干段的价值之和最小是多少。
\(f[i] = min(f[i],f[j]+max(h[j+1],h[j+2]...h[i]))\) (\(sum[i]-sum[j]\leq m\))
考虑怎么把转移优化一下。
设 \(a[i][j] = max(h[j+1],h[j+2]....h[i])\) 。
转移方程就可以写成: $f[i] = min(f[i],f[j]+a[i][j]) $。
显然 \(a[i][j] = max(a[i-1][j],h[i])\)。
设 \(ls[i]\) 表示第一个大于 \(h[i]\) 的数所在的位置,可以用单调栈来维护出来。
不难发现,加入第 \(i\) 个元素时,只会使 \((ls[i],i-1)\) 这一段的 \(a[i][j]\) 变为 \(h[i]\),其他位置不变为 \(a[i-1][j]\),相当于是线段树的区间覆盖操作。
之后用线段树维护一下 \(f[j]\) 和 \(f[j]+a[i][j]\) 的最小值即可。
code
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N = 1e5+10;
const int inf = 1e15+10;
int n,L,res,top;
int h[N],sum[N],sta[N],ls[N];
struct node
{
int lc,rc;
int sum,mx,tag;
}tr[N<<2];
#define l(o) tr[o].lc
#define r(o) tr[o].rc
#define sum(o) tr[o].sum
#define mx(o) tr[o].mx
#define tag(o) tr[o].tag
inline int read()
{
int s = 0, w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
void up(int o)
{
sum(o) = min(sum(o<<1),sum(o<<1|1));
mx(o) = min(mx(o<<1),mx(o<<1|1));
}
void cover(int o,int val)
{
tag(o) = val;
sum(o) = mx(o) + val;
}
void down(int o)
{
if(tag(o))
{
cover(o<<1,tag(o));
cover(o<<1|1,tag(o));
tag(o) = 0;
}
}
void build(int o,int L,int R)
{
l(o) = L, r(o) = R;
if(L == R)
{
sum(o) = mx(o) = inf;
return;
}
int mid = (L + R)>>1;
build(o<<1,L,mid);
build(o<<1|1,mid+1,R);
up(o);
}
void chenge(int o,int l,int r,int val)
{
if(l <= l(o) && r >= r(o))
{
cover(o,val);
return;
}
down(o);
int mid = (l(o)+r(o))>>1;
if(l <= mid) chenge(o<<1,l,r,val);
if(r > mid) chenge(o<<1|1,l,r,val);
up(o);
}
void modify(int o,int x,int val)
{
if(l(o) == r(o))
{
sum(o) = mx(o) = val;
return;
}
down(o);
int mid = (l(o)+r(o))>>1;
if(x <= mid) modify(o<<1,x,val);
if(x > mid) modify(o<<1|1,x,val);
up(o);
}
int query(int o,int l,int r)
{
int res = inf;
if(l <= l(o) && r >= r(o)) return sum(o);
down(o);
int mid = (l(o)+r(o))>>1;
if(l <= mid) res = min(res,query(o<<1,l,r));
if(r > mid) res = min(res,query(o<<1|1,l,r));
return res;
}
signed main()
{
// freopen("b.in","r",stdin);
// freopen("b.out","w",stdout);
n = read(); L = read();
for(int i = 1; i <= n; i++)
{
h[i] = read();
sum[i] = sum[i-1] + read();
}
sta[++top] = 0;
for(int i = 1; i <= n; i++)
{
while(top && h[sta[top]] <= h[i]) top--;
ls[i] = sta[top]; sta[++top] = i;
}
build(1,0,n);
modify(1,0,0);
for(int i = 1; i <= n; i++)
{
chenge(1,ls[i],i-1,h[i]);
int pos = lower_bound(sum,sum+n+1,sum[i]-L)-sum;
res = query(1,pos,i-1);
modify(1,i,res);
}
printf("%lld\n",res);
return 0;
}

浙公网安备 33010602011771号