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)\),因此超时了.

posted @ 2019-10-08 16:27  TaylorSwift13  阅读(231)  评论(0编辑  收藏  举报