NOIP模拟赛2019.8.28题解

T1 地球发动机(earth)

在这里插入图片描述

50pts

动态规划

f [ i ] f[i] f[i]表示选择第i台发动机作为最后一台发动机,前i台发动机最大的总功率

f [ i ] f[i] f[i]可以从 f [ j ] f[j] f[j]转移,当且仅当 a [ j ] + x [ j ] &lt; a [ i ] a[j]+x[j]&lt;a[i] a[j]+x[j]<a[i]

对于每一个 i i i扫描小于 i i i的所有 j j j,转移DP。

后面几个点会超时。

代码如下:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100005;
int n;
ll a[N],p[N],to[N],f[N],ans; 
ll read(){
	ll sum=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
	return sum*f;
}
void print(ll x)
{
	if(x<0)putchar('-'),x=-x;
	if(x>9)print(x/10);
	putchar(x%10+'0');
}
int main(){
//	freopen("earth.in","r",stdin);
//	freopen("earth.out","w",stdout); 
	n=read();
	int x;
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		p[i]=read();
		x=read();to[i]=a[i]+x;
	}
	for(int i=1;i<=n;i++)
	{
	  for(int j=1;j<i;j++)
	  if(to[j]<a[i])f[i]=max(f[j],f[i]);
	  f[i]+=p[i];
	  ans=max(ans,f[i]);
	}
	print(ans);
	return 0;
}

100pts

和50pts思路一样,只不过从后向前扫描。
对于当前每一个 i i i,在 i + 1 i+1 i+1到n中用lower_bound查找最小的 j j j满足 a [ i ] + x [ i ] &lt; a [ j ] a[i]+x[i]&lt;a[j] a[i]+x[i]<a[j]
f [ i ] = m a x ( f [ i + 1 ] , f [ j ] + p [ i ] ) f[i]=max(f[i+1],f[j]+p[i]) f[i]=max(f[i+1],f[j]+p[i])

代码如下:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100005;
int n;
ll a[N],p[N],to[N],f[N],ans; 
ll read(){
	ll sum=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
	return sum*f;
}
void print(ll x)
{
	if(x<0)putchar('-'),x=-x;
	if(x>9)print(x/10);
	putchar(x%10+'0');
}
int main(){
//	freopen("earth.in","r",stdin);
//	freopen("earth.out","w",stdout); 
	n=read();
	int x;
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		p[i]=read();
		x=read();to[i]=a[i]+x;
	}
	f[n]=p[n];
	for(int i=n-1;i>=1;i--)
	{
		int j=upper_bound(a+1,a+1+n,to[i])-a;
		f[i]=max(f[i+1],f[j]+p[i]);
	}
	print(f[1]);
	return 0;
}

T2 图(graph)

在这里插入图片描述

80pts

直接求a到b的最短路,统计并标记走过的路径。
再求c到d的最短路,对于未被标记的路径加入到答案中。
先求c到d,再求a到b也是80分

代码如下:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3002,inf=0x3f3f3f3f;
int n,m,head[N],join[N][N],into[N],las[N],v[N],vis[N];
int tot=1,x,y,a,b,c,d,ans,cnt,res,cur; 
struct edge{
	int ver,to;
}e[N*2];
void add(int x,int y)
{
	e[++tot].ver=y;
	e[tot].to=head[x];
	head[x]=tot;
}
int read(){
	int sum=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
	return sum*f;
}
void print(int x)
{
	if(x<0)putchar('-'),x=-x;
	if(x>9)print(x/10);
	putchar(x%10+'0');
}
void dfs1(int a,int b,int now,int sum){
	if(sum>=ans)return;
	if(now==b){
	   cnt=0;ans=sum;
	   while(into[now])
	   {
	   	las[++cnt]=into[now];
	   	now=e[into[now]^1].ver;
	   }
	   return;
	}
	for(int i=head[now];i;i=e[i].to)
	{
	  int y=e[i].ver;
	  if(vis[y])continue;
	  vis[y]=1;
	  into[y]=i;
	  dfs1(a,b,y,sum+1);
	  vis[y]=0;
	}
}
void dfs2(int a,int b,int now,int sum){
	if(sum>=ans)return;
	if(now==b){ans=sum;return;}
	for(int i=head[now];i;i=e[i].to)
	{
	  int y=e[i].ver;
	  if(vis[y])continue;
	  vis[y]=1;
	  if(!v[i])dfs2(a,b,y,sum+1);
	  else dfs2(a,b,y,sum);
	  vis[y]=0;
	}
}
void update(int x)
{
	if(x==1)
	for(int i=1;i<=cnt;i++)v[las[i]]=v[las[i]^1]=1;
	else
	for(int i=1;i<=cnt;i++)v[las[i]]=v[las[i]^1]=0;
}
int main(){
//	freopen("graph.in","r",stdin);
//	freopen("graph.out","w",stdout); 
	n=read();m=read();
	a=read();b=read();c=read();d=read();
	for(int i=1;i<=m;i++)
	{
		x=read();y=read();
		if(join[x][y])continue;	
		add(x,y);add(y,x);
		join[x][y]=join[y][x]=1;
	}
	ans=inf;
	vis[a]=1;dfs1(a,b,a,0);vis[a]=0;
	cur=ans;update(1);
	ans=inf;
	vis[c]=1;dfs2(c,d,c,0);vis[c]=0;
	res=cur+ans;update(0);
    print(res);
	return 0;
}

100pts

分两种情况:
(1):a-b,c-d 的路径不相交,则答案一定是 a-b,c-d 的最短路之和。
(2):a-b,c-d 的路径相交,则相交部分一定是连续的一段路径,
于是 O ( n 2 ) O(n^2) O(n2)预处理每对点的最短路,再 O ( n 2 ) O(n^2) O(n2)枚举相交的路径的两端,算一下总长就好了。
以上两种情况所有方案取最小值即可。

时间复杂度 O ( n 2 O(n^2 O(n2

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3002,inf=0x3f3f3f3f;
int n,m,a,b,c,d,d1[N],d2[N],d3[N],d4[N],dd[N];
int join[N][N],v[N],tot,head[N],res;
queue<int>q;
struct edge{
	int ver,to;
}e[N*2];
void add(int x,int y)
{
	e[++tot].ver=y;
	e[tot].to=head[x];
	head[x]=tot;
}
int read(){
	int sum=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
	return sum*f;
}
void print(int x)
{
	if(x<0)putchar('-'),x=-x;
	if(x>9)print(x/10);
	putchar(x%10+'0');
}
void spfa(int x,int *d)
{
  memset(d,inf,sizeof(d));
  memset(v,0,sizeof(v));
  q.push(x);
  d[x]=0;
  v[x]=1;
  while(q.size()){
  	int x=q.front();
  	q.pop();
  	for(int i=head[x];i;i=e[i].to)
  	{
  	 int y=e[i].ver;
	 if(v[y])continue;
	 d[y]=d[x]+1;v[y]=1;q.push(y);
	}
  }
}
int main(){
//	freopen("graph.in","r",stdin);
//	freopen("graph.out","w",stdout); 
	n=read();m=read();
	a=read();b=read();c=read();d=read();
	int x,y;
	for(int i=1;i<=m;i++)
	{
		x=read();y=read();
		add(x,y);add(y,x);
	}
	spfa(a,d1);	spfa(b,d2);spfa(c,d3);spfa(d,d4);
	res=d1[b]+d3[d];
	for(int i=1;i<=n;i++)
	{
		spfa(i,dd);
		for(int j=i;j<=n;j++)
		{
			res=min(res,min(d1[i]+d3[i]+d2[j]+d4[j],d1[j]+d3[j]+d2[i]+d4[i])+dd[j]);
			res=min(res,min(d1[i]+d4[i]+d2[j]+d3[j],d1[j]+d4[j]+d2[i]+d3[i])+dd[j]);
		}
	}
	print(res);
	return 0;
}

T3 树(tree)

在这里插入图片描述

100pts:

首先每一个二进制位可以拆开来看,对答案没有影响。
树形DP,对每个二进制位分别统计对答案的影响。记 f [ x ] [ i ] [ j ] [ 0 / 1 ] f [x][i][j][0/1] f[x][i][j][0/1]为当前以x为根的子树中,深度为i的所有节点的第j位是 0 / 1 0/1 0/1的数量。每将一个儿子y合并上去时,先利用前缀和统计两点分别在当前以x为根的子树和以y为根的子树中的答案,再将y的信息合并进x中。
还要用点分治优化,每次统计答案时都只会用线段树统计后缀和,而且发现第二维状态大小只和当前子树深度有关,于是使用长链剖分,统计后缀和的方法合并子树信息和统计答案。

代码如下:


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=500005;
int size,firs[N],n,k,dep[N],son[N],cnt=1;
struct node{int f[20][2];}b[N],a[N],sa,tb;
node *f[N];
long long ans;
struct edge{int u,ver,to;}e[N*2];
ll read(){
	ll sum=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
	return sum*f;
}
node operator+(node a,int b){
	for (int i=0;i<20;i++){a.f[i][b&1]++;b>>=1;}
	return a;
}
void operator+=(node &a,node b){
	for (int i=0;i<20;i++){a.f[i][0]+=b.f[i][0];a.f[i][1]+=b.f[i][1];}
}
node operator+(node a,node b){a+=b;return a;}
node operator-(node a,node b){
	for(int i=0;i<20;i++){a.f[i][0]-=b.f[i][0];a.f[i][1]-=b.f[i][1];}
	return a;
}
ll operator*(node &a,node &b){
	ll ans=0;
	for (int i=0;i<20;i++)ans+=((ll)a.f[i][0]*b.f[i][1]+(ll)a.f[i][1]*b.f[i][0])<<i;
	return ans;
}
void add(int u,int ver){e[++size]=(edge){u,ver,firs[u]};firs[u]=size;}
void dfs(int x,int fa){
	dep[x]=1;
	for (int i=firs[x];i;i=e[i].to)
	if (e[i].ver!=fa){
		int y=e[i].ver;dfs(y,x);
		if (dep[y]+1>dep[x]){
			dep[x]=dep[y]+1;
			son[x]=y;
		}
	}
}
node calc(int x,int i){
	if (i<0) return f[x][0];
	return (dep[x]<=i) ? b[0] : f[x][i];
}
node& calc(int x,int l,int r,node &a){return a=calc(x,l)-calc(x,r+1);}
void merge(int x,int y){
	for (int i=0;i<dep[y];i++)
	ans+=calc(y,i,i,sa)*calc(x,0,k-i-1,tb);
	for (int i=0;i<dep[y];i++)
	f[x][i+1]+=f[y][i];
	f[x][0]+=f[y][0];
}
void update(int x){f[x]=b+cnt;cnt+=dep[x];}
void dp(int x,int fa){
	if (son[x]){
		f[son[x]]=f[x]+1;
		dp(son[x],x);
		f[x][0]=f[x][1]+a[x];
		ans+=a[x]*calc(x,0,k,sa);
	}
	else f[x][0]=a[x];
	for (int i=firs[x];i;i=e[i].to)
	if ((e[i].ver!=fa)&&(e[i].ver!=son[x])){
		int y=e[i].ver;update(y);dp(y,x);
		merge(x,y);
	}
}
int main(){
//	freopen("tree.in","r",stdin);
//	freopen("tree.out","w",stdout);
	n=read();k=read(); 
	for (int i=1;i<=n;i++){
		int x=read();a[i]=a[i]+x;
	}
	for (int i=1;i<n;i++){
		int u=read(),ver=read();add(u,ver);add(ver,u);
	}
	dfs(1,0);update(1);dp(1,0);
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值