2019/10/6 校内模拟赛 考试报告
A.小明的新书(Book.cpp)
题目
问题描述
小明即将出版新书,以记录他辉煌的虐题生涯。
有 \(n\) 家出版社对这本书表示了兴趣,并愿意给小明支付 \(p\in [\text{Min_pay},\text{Max_pay}]\)的报酬来得到这本书的出版权,每家出版社的\(\text{Min_pay}\) 和 \(\text{Max_pay}\) 是不一样的。
现在小明希望你帮他找出一个报酬值 \(p\),使得他获得的总报酬最多(每一个 \(\text{Min_pay}\le p\le \text{Max_pay}\) 的出版社都会付给小明 \(p\) 的报酬)。
输入文件
第一行为一个整数 \(n\)。
接下来 \(n\) 行每行 2 个整数 \(\text{Min_pay_i}\) 和 \(\text{Max_pay_i}\),为第 \(i\) 家出版社愿支付的报酬范围。
输出文件
只有一个整数 \(ans\),为最大总报酬。
输入样例
4
1 3
2 4
3 5
4 7
输出样例
12
样例解释
当 \(p=4\) 时,有 3 家出版社会给出报酬,此时总报酬最大。
数据规模与约定
\(1\le n\le 100000,1\le \text{Min_pay},\text{Max_pay}\le 10^9\).
题意
给定\(n\)个区间,设\(num(p)\)为包含值\(p\)的区间的个数,求\(p\times num(p)\)的最大值.
解法
把区间的左右端点一起离散化到\([0,2n]\)的范围内.
用一个序列\(x[]\)去维护这些区间,对于一个区间,把\(x[L],x[L+1],...,x[R]\)全部\(+1\),那么\(x[i]\)的值就表示有多少个区间包含\(i\).
扫描\(x[]\),用\(Ref(i)\times x[i]\)更新最大值即可.其中\(Ref(i)\)表示\(i\)离散化之前的值.
代码中使用了差分数组维护\(x[]\)的区间加法与单点查询.
代码
#include<bits/stdc++.h>
const int SIZE=500005;
int n,A[SIZE],B[SIZE],Tem[SIZE],D[SIZE],Tot;
long long Ans,now;
int main()
{
freopen("Book.in","r",stdin);
freopen("Book.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&A[i],&B[i]);
Tem[++Tot]=A[i];
Tem[++Tot]=B[i];
}
std::sort(Tem+1,Tem+1+Tot);
for(int i=1;i<=n;i++)
{
A[i]=std::lower_bound(Tem+1,Tem+1+Tot,A[i])-Tem;
B[i]=std::lower_bound(Tem+1,Tem+1+Tot,B[i])-Tem;
}
for(int i=1;i<=n;i++)
{
++D[A[i]];
--D[B[i]+1];
}
for(int i=1;i<=2*n;i++)
{
now+=D[i];
Ans=std::max(Ans,now*Tem[i]);
}
printf("%lld",Ans);
return 0;
}
B.小明的 Au Plan(Plan.cpp)
题目
问题描述
小明为了变得越来越神,他给自己制定了 \(n\) 个任务,编号为 \(1,2,...,n\)。小明在完成这些任务之前有一个初始兴奋值 \(m\),每个任务都有一个难度值 \(hard[i]\),且对于任何 \(i>j\),有 \(hard[i]>hard[j]\),小明完成第 \(i\) 个任务,兴奋值至少会减少 \(hard[i]\),第 \(i\) 个任务完成之后,小明会受到鼓舞,兴奋值又会增加 \(s[i]\),每个任务只完成一次。小明可以一次完成所有剩余的难度值不超过现有兴奋度的任务,这样只会消耗那个最大的难度值。现在小明想知道完成这 \(n\) 个任务之后,他的最大兴奋值为多少。
输入文件
第一行 2 个整数 \(n,m\),如题意。
第二行 n 个整数,第 \(i\) 个为 \(hard[i]\)。
第三行 n 个整数,第 \(i\) 个为 \(s[i]\)。
输出文件
一个整数,为完成这 n 个任务之后的最大兴奋值。
输入样例
5 5
2 4 5 7 9
4 4 3 6 5
输出样例
14
样例解释
第一次完成前 2 个任务,兴奋值为 9;第二次完成后 3 个任务,兴奋值为 14。
数据规模与约定
对于 100%的数据,1<=n<=200000,数据保证所有任务都能完成。
解法
首先考虑最基本的DP.
设\(DP[i]\)表示完成前\(i\)个任务后,最大的兴奋值.
那么\(DP[0]=m\).答案为\(DP[n]\).
有\(O(n^2)\)的DP解法:
for(int i=1;i<=n;i++)
for(int k=0;k<i;k++)
if(DP[k]>=h[i])
DP[i]=std::max(DP[i],DP[k]-h[i]+sum[i]-sum[k])
发现状态转移方程中,\((DP[k]-sum[k])\)仅与\(k\)有关,\((sum[i]-h[i])\)仅与\(i\)有关,没有与\(i,k\)同时相关的项,可以使用单调队列优化DP,但是需要处理\(DP[k]>=h[i]\)这一限制.
由于\(h[]\)单调上升,只要\(DP[k]<h[i]\),那么对于\(j\in [i+1,n]\),一定都有\(DP[k]<h[j]\),换句话说,从\(i\)往后的所有\(DP[]\)的值,都不可能由\(DP[k]\)更新.那么直接舍弃\(k\)就可以了.
用单调队列优化DP,时间复杂度\(O(n)\),但由于我太菜了😢,不会单调队列,采用了优先队列维护的方法,时间复杂度\(O(nlogn)\).
代码
#include<bits/stdc++.h>
#define LL long long
const int SIZE=200005;
int n;
LL h[SIZE],s[SIZE],DP[SIZE],sum[SIZE];
struct node
{
LL A;//DP[k]
LL B;//DP[k]-sum[k]
bool operator <(const node &x)const
{
if(B!=x.B)return B<x.B;
else return A<x.A;
}
};
std::priority_queue<node>q;
int main()
{
freopen("Plan.in","r",stdin);
freopen("Plan.out","w",stdout);
scanf("%d%lld",&n,&DP[0]);
for(int i=1;i<=n;i++)
scanf("%lld",&h[i]);
for(int i=1;i<=n;i++)
{
scanf("%lld",&s[i]);
sum[i]=sum[i-1]+s[i];
}
q.push((node){DP[0],DP[0]-sum[0]});
for(int i=1;i<=n;i++)
{
node Tem=q.top();
while(Tem.A<h[i])
{
q.pop();
Tem=q.top();
}
DP[i]=Tem.B+sum[i]-h[i];
q.push((node){DP[i],DP[i]-sum[i]});
}
printf("%lld",DP[n]);
return 0;
}
C.小明的讲学计划(Teach.cpp)
题目
问题描述
小明为了造福社会,准备到各地去讲学。他的计划中有 n 个城市,从 u 到 v 可能有一条单向道路,通过这条道路所需费用为 q。当小明在 u 城市讲学完之后,u 城市会派出一名使者与他同行,只要使者和他在一起,他到达某个城市就只需要花 1 的入城费且只需交一次,在路上的费用就可免去。但是使者要回到 u 城市,所以使者只会陪他去能找到回 u 城市的路的城市。小明从 1 号城市开始讲学,若他在 u 号城市讲学完毕,使者会带他尽可能多的去别的城市。他希望你帮他找出一种方案,使他能讲学到的城市尽可能多,且费用尽可能小。
输入文件
第一行 2 个整数 n,m。
接下来 m 行每行 3 个整数 u,v,q,表示从 u 到 v 有一条长度为 q 的单向道路。
输出文件
一行,两个整数,为最大讲学城市数和最小费用。
输入样例
6 6
1 2 3
2 3 7
2 6 4
3 4 5
4 5 4
5 2 3
输出样例
6 10
样例解释
从 1 走到 2,2 城市使者会带他到 3,4,5 城市,
回到 2 城市,再走到 6,总费用为 3+3+4=10。
数据规模与约定
对于 100%的数据,1<=n<=100000 1<=m<=500000,1<=q<=1000, 保证无自环无重边。
解法
Tarjan求出强连通分量,显然如果进入了一个强连通分量,遍历这个强连通分量内的所有城市一定是最优方案.将强连通分量缩点得到\(G'\),将从\(Belong[1]\)节点出发能到达的所有节点提出来得到\(G''\),在\(G''\)中从\(Belong[1]\)出发,用拓扑排序求最长链即可.
代码
#include<bits/stdc++.h>
const int SIZE=100005;
int n,m,head[SIZE],nex[500005],fo[500005],to[500005],edge[500005],Tot;
int DFN[SIZE],Low[SIZE],Tim;
int Sta[SIZE],Top;
bool InS[SIZE];
int B[SIZE],Cnt,Siz[SIZE],InD[SIZE];
int head2[SIZE],nex2[500005],to2[500005],edge2[500005],Tot2;
void Link(int u,int v,int q)
{
nex[++Tot]=head[u];head[u]=Tot;fo[Tot]=u;to[Tot]=v;edge[Tot]=q;
}
void Link2(int u,int v,int q)
{
nex2[++Tot2]=head2[u];head2[u]=Tot2;to2[Tot2]=v;edge2[Tot2]=q;
}
void Tarjan(int u)
{
Sta[++Top]=u;
DFN[u]=Low[u]=++Tim;
InS[u]=1;
for(int i=head[u];i;i=nex[i])
{
int v=to[i];
if(!DFN[v]){Tarjan(v);Low[u]=std::min(Low[u],Low[v]);}
else if(InS[v])Low[u]=std::min(Low[u],DFN[v]);
}
if(DFN[u]==Low[u])
{
++Cnt;
do
{
B[Sta[Top]]=Cnt;
Siz[Cnt]++;
InS[Sta[Top]]=0;
}while(Sta[Top--]!=u);
}
}
std::queue<int>q;
struct node
{
int num,C;
bool operator <(const node &x)const
{
if(num==x.num)return C>x.C;
return num<x.num;
}
};
node DP[SIZE],Ans;
bool mk[SIZE];
void DFS(int u)
{
mk[u]=1;
for(int i=head2[u];i;i=nex2[i])
{
int v=to2[i];
DFS(v);
}
}
int main()
{
freopen("Teach.in","r",stdin);
freopen("Teach.out","w",stdout);
scanf("%d%d",&n,&m);
int u,v,e;
while(m--){scanf("%d%d%d",&u,&v,&e);Link(u,v,e);}
for(int i=1;i<=n;i++)if(!DFN[i])Tarjan(i);
for(int i=1;i<=Tot;i++)
{
u=fo[i];
v=to[i];
if(B[u]!=B[v])
{
Link2(B[u],B[v],edge[i]);
//InD[B[v]]++;
}
}
DFS(B[1]);
for(int i=1;i<=Tot;i++)
{
u=fo[i];
v=to[i];
if(B[u]!=B[v])
{
if(mk[B[u]]&&mk[B[v]])
InD[B[v]]++;
}
}
DP[B[1]].num=Siz[B[1]];
DP[B[1]].C=Siz[B[1]]-1;
//InD[B[1]]=0;
q.push(B[1]);
while(q.size())
{
u=q.front();
q.pop();
for(int i=head2[u];i;i=nex2[i])
{
v=to2[i];
node Tem=(node){DP[u].num+Siz[v],DP[u].C+edge2[i]+Siz[v]-1};
if(DP[v]<Tem)DP[v]=Tem;
--InD[v];
if(InD[v]==0)q.push(v);
}
}
for(int i=1;i<=Cnt;i++)
if(Ans<DP[i])Ans=DP[i];
printf("%d %d",Ans.num,Ans.C);
return 0;
}
回顾与讨论
但是,在实际测试中,我们发现这样的解法只得到了 50 分,这是为什么呢?
原来,在将从\(Belong[1]\)节点出发能到达的所有节点提出来得到\(G''\)这一步中,我是这么写的:
void DFS(int u)
{
mk[u]=1;
for(int i=head2[u];i;i=nex2[i])
DFS(to2[i]);
}
实际上,这么写才能 AC 本题:
void DFS(int u)
{
if(mk[u])return;
mk[u]=1;
for(int i=head2[u];i;i=nex2[i])
DFS(to2[i]);
}
这是一个 DAG 图,菜爆了的我认为,反正都没有环,不用加上\(\text{if(mk[u])return;}\).但是,如果去掉这句话,在 DFS 过程中,每个节点很可能不止访问一次, DFS 的时间复杂度不能保证为\(O(n)\),因此超时了.