隔壁的水题,下午和我们班的人一起做
本来停课的欢乐得很,然后教练突然考试(一般都是上午考),把我们吓尿了
然后教练又说三个小时
闻风丧胆......
心态血崩
然后AK了2333333333
题目真的好水啊.....
T1
题目描述
一眼秒杀
我们可以首先发现一个事实,首先我们假设一个任务的截止时间为s,完成它需要的时间是t
那么对于任何一个截止时间,最晚的开始时间就是s-n,那么该任务就可以形成一个最优区间[s-t,s]
然后我们可以考虑将所有的s从小到大排一遍序,然后每一次询问的时候比较它的最优区间和之前的最优区间
我们可以发现他们之间并不是一定重叠的,而只要不是重叠的区间,那么中间的空白位置就都可以通过用来平移
所以我们在扫描的时候直接记录当前位置可以调用的空白位置的和sum
如果发现重叠,我们就尝试将它往前平移,那么sum就会减少,然后每一次平移都比较一次ans会不会变得更小(一直平移使得最早的区间也发生了平移使得ans变小)
由于排序的影响使得复杂度变成了O(nlog(n))
据说有二分答案的做法,但是我完全不知道......
总而言之愉快地切掉了,耗时7mins
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define OP "manage"
const int MAXN=1e5+5;
const int INF=1e9+7;
int n;
int ans=INF;
int sum;
struct Task
{
int t;
int s;
bool operator <(const Task &z)const
{
return s<z.s;
}
}a[MAXN];
int main()
{
std::freopen(OP".in","r",stdin);
std::freopen(OP".out","w",stdout);
std::scanf("%d",&n);
for(int i=1;i<=n;i++)
{
std::scanf("%d%d",&a[i].t,&a[i].s);
}
std::sort(a+1,a+1+n);
sum=0;
int flag=0;
for(int i=1;i<=n;i++)
{
if(sum+a[i].t>a[i].s)
{
flag=1;
goto NXT;
}
sum+=a[i].t;
ans=std::min(ans,a[i].s-sum);
}
if(!flag)
{
std::printf("%d\n",ans);
return 0;
}
NXT:;
std::printf("-1\n");
return 0;
}
/*
4
3 5
8 14
5 20
1 16
*/
T2
题目描述
考完试后发现是河北10年的省选题,在洛谷上还霸占着紫题的彪悍位置
然而名不副实,非常水..............
我们可以发现,如果说没有依赖关系,就是普通01背包问题
报告教练我会for循环了!.jpg
如果说只有直接依赖,没有间接依赖,那么就是一个很简单的分组背包问题
报告教练我会使用数组了!.jpg
所以说本题的关键问题都在这一个间接依赖的关系
由于选择了一个程序,必须要选择它依赖的程序以及该程序必须依赖的程序......
稍作观察可以发现这是一个树形结构,相当于每选择一个点必须将其所有祖先全部选择一遍
那么我们可以在树上跑一遍背包
定义dp[u][j]为以u为根的子树在选择重量和为j的背包下最多能够获得的价值
直接for一遍子节点dfs转移即可
但是
但是
但是
中国人讲但是后面的话比较重要
这道题目还有坑...........................
由于题目中没有告诉你是否会存在相互依赖的关系
所以可能会成环....比如1依赖2,2依赖1的这种特殊情况
然后我们会再一次发现,由于每一个物体都只有一个依赖物体,所以所有的环缩点之后都会在原来的树上
那么我们可以跑一遍tarjan,然后将环缩成的大点的重量和价值都看作是这个联通分量内的所有点的和
然后就可以轻松愉快跑dp啦.........
T2耗时35mins
贴上丑陋的代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define OP "software"
const int MAXN=2e3+5;
const int NN=1e4+5;
class Edge
{
public:
int nxt;
int to;
}edge[NN<<1];
int head[MAXN];
int num;
void add(int from,int to)
{
edge[++num].nxt=head[from];
edge[num].to=to;
head[from]=num;
}
class Obj
{
public:
int v;
int w;
int d;
}a[MAXN];
int n,m;
int indx;
int dfn[MAXN];
int low[MAXN];
int stk[MAXN];
int color[MAXN];
bool vis[MAXN];
int cnt=0;
int top=0;
int wei[MAXN];
int val[MAXN];
int dp[2005][505];
void tarjan(int x)
{
low[x]=dfn[x]=++indx;
vis[x]=1;
stk[++top]=x;
for(int i=head[x];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(!dfn[v])
{
tarjan(v);
low[x]=std::min(low[x],low[v]);
}
else
if(vis[v])
{
low[x]=std::min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x])
{
cnt++;
while(1)
{
int u=stk[top--];
color[u]=cnt;
vis[u]=0;
val[cnt]+=a[u].v;
wei[cnt]+=a[u].w;
if(u==x)
{
break;
}
}
}
}
int indgr[MAXN];
void dfs(int u,int f)
{
for(int i=wei[u];i<=m;i++)
{
dp[u][i]=val[u];
}
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(v==f)
{
continue;
}
dfs(v,u);
for(int j=m-wei[u];j>=0;j--)
{
for(int k=0;k<=j;k++)
{
dp[u][j+wei[u]]=std::max(dp[u][j+wei[u]],dp[u][j+wei[u]-k]+dp[v][k]);
}
}
}
}
int main()
{
std::freopen(OP".in","r",stdin);
std::freopen(OP".out","w",stdout);
std::scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
std::scanf("%d",&a[i].w);
}
for(int i=1;i<=n;i++)
{
std::scanf("%d",&a[i].v);
}
for(int i=1;i<=n;i++)
{
std::scanf("%d",&a[i].d);
add(a[i].d,i);
}
for(int i=0;i<=n;i++)
{
if(!dfn[i])
{
tarjan(i);
}
}
std::memset(head,0,sizeof(head));
num=0;
std::memset(edge,0,sizeof(edge));
for(int i=1;i<=n;i++)
{
int u=a[i].d;
int v=i;
if(color[u]!=color[v])
{
add(color[u],color[v]);
add(color[v],color[u]);
indgr[color[v]]=1;
}
}
for(int i=1;i<=cnt;i++)
{
if(!indgr[i])
{
add(0,i);
add(i,0);
}
}
dfs(0,0);
std::printf("%d\n",dp[0][m]);
return 0;
}
/*
3 10
5 5 6
2 3 4
0 1 1
4 27
14 13 14 11
5020 39 15 15
0 1 2 3
*/
T3
题目描述
其实它甚至比T2更加的水............................
我们可以首先发现这道题目的要求其实是求一个贪心的斜率上升的序列
打个比方
设现在有
如果Xi<Xj且(0,0)和i的最高点所形成的边的斜率小于原点和j的最高点形成的边,那么就可以同时看见i和j,然后我们就可以发现它其实就是要让我们维护一个序列,该序列支持两种操作
1、在某一个位置i加入一条新的柱子
2、求当前的所有序列里面可以看见的柱子的数量
一个柱子能被看到当且仅当它前面的柱子斜率都比它小
那么我们可以把这道题抽象成这么一个序列的问题
对于这种信息可以很轻松的想到用分块或者线段树来维护
考场上两个方法都想到了,但是写了线段树
其实分块是更简单好写的
类似与分块的入门题目洛谷P2801教主的魔法
我们可以每次修改之后,将每一个快都维护成为一个单调递增的块。然后对于边缘的零散块就在原来的序列上面暴力修改
然后我们就可以发现每一次查询可以对右端的零散区间在原序列暴力查找,然后每个块内二分用lowerbound来查询比当前最大斜率大的柱子的个数,然后更新这个值,再继续跳下一个块
优势是简单无脑,劣势是如果写得比较丑或者常数比较大的会非常容易被卡常
所以个人还是写了线段树
由于线段树的思维要稍微开阔一些,所以说代码其实更短,但并不是很无脑的代码,所以说写起来反而比较复杂
对于线段树每个结点维护两个值:sum和vmax,sum表示只考虑这个区间的时候可以看见的柱子数量(也就是说假设整条线段上的所有点上面都没有柱子,只有这个区间里面加过柱子的地方有柱子的情况下可以见的柱子数量),vmax表示这个区间的最大斜率。
那么问题的关键就在于如何合并两个区间,显然左区间的答案肯定可以作为总区间的答案了,因为当前的左区间在合并的时候是没有约束条件的。那么接下来就是看右区间有多少个在新加入左区间的约束后是可行的。考虑如果右区间最大值都小于等于左区间最大值那么右区间贡献就为0,因为整个区间都被挡住了。
如果大于最大值,就再考虑右区间的两个子区间:左子区间、右子区间,加入左子区间的最大值小于等于左区间最大值,那么就递归处理右子区间;否则就递归处理左子区间,然后加上右子区间原本的答案。考虑这样做的必然性:因为加入左区间最高的比左子区间最高的矮,那么相当于是左区间对于右子区间没有约束,都是左子区间产生的约束。但是右子区间的答案要用右区间答案-左子区间答案,因为其本来考虑的是整段区间,是没有考虑约束条件的,所以说要做一个简单的容斥,也就是该区间总的sum-左子区间的sum+右子区间的合法贡献。
代码很短,耗时31mins
#include<cstdio>
#include<cstring>
#include<algorithm>
#define OP "rebuild"
const int MAXN=1e5+5;
int n,m;
class Node
{
public:
Node *ls;
Node *rs;
int sum;
double vmax;
}pool[MAXN<<1],*tail=pool,*root;
Node *newnode()
{
Node *nd=++tail;
nd->sum=0;
nd->vmax=0.0;
return nd;
}
int calc(Node *nd,int l,int r,double val)
{
if(l==r)
{
return nd->vmax>val;
}
int mid=l+r>>1;
if(val>=nd->ls->vmax)
{
return calc(nd->rs,mid+1,r,val);
}
else
{
return nd->sum-nd->ls->sum+calc(nd->ls,l,mid,val);
}
}
Node *build(int l,int r)
{
Node *nd=++tail;
if(l==r)
{
return nd;
}
else
{
int mid=l+r>>1;
nd->ls=build(l,mid);
nd->rs=build(mid+1,r);
}
return nd;
}
void modify(Node *nd,int l,int r,const int pos,const double val)
{
if(l==r)
{
nd->vmax=val;
nd->sum=1;
return;
}
int mid=l+r>>1;
if(pos<=mid)
{
modify(nd->ls,l,mid,pos,val);
}
else
{
modify(nd->rs,mid+1,r,pos,val);
}
nd->vmax=std::max(nd->ls->vmax,nd->rs->vmax);
nd->sum=nd->ls->sum+calc(nd->rs,mid+1,r,nd->ls->vmax);
}
int main()
{
//std::freopen(OP".in","r",stdin);
//std::freopen(OP".out","w",stdout);
scanf("%d%d",&n,&m);
root=build(1,n);
for(int i=1;i<=m;i++)
{
int x,y;
std::scanf("%d%d",&x,&y);
double val=(double)y/(double)x;
modify(root,1,n,x,val);
std::printf("%d\n",root->sum);
}
return 0;
}
/*
3 4
2 4
3 6
1 1000000000
1 1
*/
完成整套题的时间也就只有1个小时出头,但是这次非常的稳,每一条程序从头到尾都读了一遍,还调试了一下小样例,写了一下T2跟T3的对拍,在1h'40mins的时候差不多就完全完成了,然后就开始颓废地逛网易云了23333(忍住没有在考试的时候补番真是胜利啊hhhh)
最后还是AK了,班上有一个掉渣天的强选手T2T3都挂了的110....还有一个1h写完之后玩儿影之诗结果T2疯狂犯二被拿下70分的233333
继续加油把