【原创】2019.10.15模拟赛 旅行 数据 业务

10.15

旅行

Дракон Соколов

我对我写的区区一百篇博客每篇都视若己出,只要是我写过的(水题的)题解我是绝不会忘的。

其实我忘了,我只记得这题是个极水的题,我还写了一个40分的假贪心(为什么这么久了还能记得40这个数字?),于是盯着看了几秒在写了几分钟代码就切掉了。

要是我去年noip day1t1也能这么做就好了。

【重庆市NOIP模拟赛】旅行

时限: 1 Sec 内存: 128 MB
提交: 8 解决: 8

题目描述

Mr_H 旗下的 n 个 OIer 坐船外出旅行!
但是他们只有一艘船,虽然船能装下全部的 Oier,但太拥挤将会影响众 OIer 的心情,所以 Mr_H决定选择一部分 Oier 去。我们假设,每个人单独坐船的快乐程度是 Ci,而船上每多一个人,他的快乐程度会减去 Di。
现在你的任务是帮助 Mr_H 计算,选择那些人,才能使船上所有人的快乐程度之和达到最大。

输入格式

第 1 行是一个整数 n,表示 OIer 的人数;
第 2 行有 n 个整数,第 i 个整数表示第 i 个人人单独坐船的快乐程度 Ci(1<=Ci<=10000);
第 3 行有 n 个整数,第 i 个整数表示每多 1 人,第 i 个人快乐程度的下降值 Di(1<=Di<=10)。

输出格式

第 1 行一个整数,是最大的快乐程度之和;
第 2 行一个整数,是最大的快乐程度之和所对应的汽艇上的人数(若有多种方案,则输出人数最
多的)。

输入样例

6
10 10 10 10 10 9
2 2 2 2 2 3

输出样例

18
3

提示

前 3 个人去坐汽艇可使快乐程度之和达到最大,每个人的快乐程度均为 10-2*2=6,总和是 18。
对于 30%的数据,n<=20;
对于 100%的数据,n<=1000。

分析

才1000个人啊喂,我们枚举船上坐几个人,然后把每个人此时的心情算出来,然后就取最开心的前这么多人就好了。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

inline void Read(int &p)
{
	p=0;
	char c=getchar();
	while(c<'0' || c>'9') c=getchar();
	while(c>='0' && c<='9')
		p=p*10+c-'0',c=getchar();
}

const int MAXN=1024;
struct people
{
	int ark,del;
	bool operator < (const people &p) const
	{
		return ark>p.ark;
	}
}arr[MAXN];
int n,ans,tmp,pos;

int main()
{
	freopen("travel.in","r",stdin);
	freopen("travel.out","w",stdout);
	Read(n);
	for(int i=1;i<=n;i++) Read(arr[i].ark);
	for(int i=1;i<=n;i++) Read(arr[i].del);
	
	for(int lim=1;lim<=n;lim++)
	{
		tmp=0,sort(arr+1,arr+1+n);
		for(int i=1;i<=lim;i++) tmp+=arr[i].ark;
		if(tmp>=ans) ans=tmp,pos=lim;
		for(int i=1;i<=n;i++) arr[i].ark-=arr[i].del;
	}
	printf("%d\n%d\n",ans,pos);
}

数据

题目描述

Mr_H 出了一道信息学竞赛题,就是给 n 个数排序。输入格式是这样的:
试题有若干组数据。每组数据的第一个是一个整数 n,表示总共有 n 个数待排序;接下来 n 个整数,分别表示这 n 个待排序的数。
例如:3 4 2 –1 4 1 2 3 4,就表示有两组数据。第一组有 3 个数(4,2,-1),第二组有 4个数(1,2,3,4)。可是现在 Mr_H 做的输入数据出了一些问题。例如:2 1 9 3 2 按理说第一组数据有 2 个数(1,9),第二组数据有 3 个数,可是“3”后面并没有出现三个数,只出现了一个数“2”而已!
现在 Mr_H 需要对数据进行修改,改动中“一步”的含义是对文件中的某一个数+1 或-1,写个程序,计算最少需要多少步才能将数据改得合法。

输入格式

第一行一个整数 m,表示 Mr_H 做的输入数据包含的整数个数。第二行包含 m 个整数 a[i],每
个整数的绝对值不超过 10000。

输出格式

一个整数,表示把数据修改为合法的情况下,最少需要多少步。

输入样例

4
1 9 3 2

输出样例

2

提示

对于 20%的数据,m<=10, |a[i]|<=5;
对于 60%的数据,m<=5000, |a[i]|<=10000
对于 100%的数据,m<=100000, |a[i]|<=10000

分析

可能我层次比较低,我个人感觉这道题的建图方法是比较,比较巧妙的。我自己居然想到了,这也是比较,比较巧妙的。

i i i个数,假设它的值为 a i a_i ai,而且 a i > 0 a_i\gt 0 ai>0,那么就是说 [ i + 1 , i + a i + 1 ] [i+1,i+a_i+1] [i+1,i+ai+1]这一段是被它囊括了的,而且不需要调整 a i a_i ai的值,所以我们从 i i i i + a i + 1 i+a_i+1 i+ai+1连一条边长为 0 0 0的边,代表我走 0 0 0步就能从 i i i走到 i + a i + 1 i+a_i+1 i+ai+1这个点。
那么我们怎么刻画题目中说的“改正”?我们把 a i + 1 a_i+1 ai+1,在图上有什么意义?就是说我们走了这一步以后就走到 i + a i + 2 i+a_i+2 i+ai+2了,那么我们就从 i + a i + 1 i+a_i+1 i+ai+1 i + a i + 2 i+a_i+2 i+ai+2连一条长度为 1 1 1的边。当然 a i − 1 a_i-1 ai1的边可以由 i + a i i+a_i i+ai代劳,而 a i + 2 a_i+2 ai+2可以由 i + a i + 1 → i + a i + 2 → i + a i + 3 i+a_i+1\to i+a_i+2 \to i+a_i+3 i+ai+1i+ai+2i+ai+3这么走。
所以实际操作的时候我们就把每个点和它的下一个点连双向边即可,如果按上面这么整的话可能会有点访问不到;注意还可能会走到外面。

那么答案就是1到n+1的最短路(必须是n+1,如果超出去了你就走回来)。

非常担心边会连太多,于是忧心忡忡地算啊算啊,保守的开了八十万。
于是我点数开小了,RE,只得了85分。

看来是我的境界太低了。

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

inline void Read(int &p)
{
	p=0;
	int f=1;
	char c=getchar();
	while(c<'0' || c>'9') 
	{
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0' && c<='9')
		p=p*10+c-'0',c=getchar();
	p*=f;
}

const int MAXN=1002030,MAXM=802030;

queue<int>q;
bool inq[MAXN];
int n,u,v,mx,arr,ans,cnt,dis[MAXN],head[MAXN],nxt[MAXM],to[MAXM],val[MAXM];

inline void addedge(int u,int v,int w) {nxt[++cnt]=head[u],head[u]=cnt,to[cnt]=v,val[cnt]=w;}

int main()
{
	freopen("data.in","r",stdin);
	freopen("data.out","w",stdout);
	
	Read(n),Read(arr),mx=max(mx,arr+2); 
	if(arr<0) ans=-arr,addedge(1,2,0);
	else addedge(1,arr+2,0);
	for(int i=2;i<=n;i++)
	{
		Read(arr),mx=max(mx,i+arr+1);
		if(arr>=0) addedge(i,i+arr+1,0);
		addedge(i,i+1,1),addedge(i+1,i,1);
	}
	for(int i=n+1;i<mx;i++) addedge(i,i+1,1),addedge(i+1,i,1);
	
	memset(dis,0x3f,sizeof dis);
	dis[1]=0,q.push(1),inq[1]=1;
	while(!q.empty())
	{
		u=q.front(),inq[u]=0,q.pop();
		for(int i=head[u];i;i=nxt[i])
			if(dis[to[i]]>dis[u]+val[i])
			{
				dis[to[i]]=dis[u]+val[i];
				if(!inq[to[i]]) q.push(to[i]),inq[to[i]]=1;
			}
	}
	printf("%d\n",ans+dis[n+1]);
}

本题还有DP的做法。
大概就是见下图。

业务

题目描述

Mr_H 谋得一份兼职——货车司机,从此以后他将会开着货车穿行在 C 国的各大城市之间。
C 国中有 n 座城市(编号为 1~n),并且有 m 条双向公路,每条公路连接两座不同的城市。货车从任意一座城市出发都可以抵达任意另一座城市。在每条公路上,都有一个收费站,通过的车辆需要交纳一定过路费。可能有多条公路连接相同的两座城市。
为了增加财政收入,C 国还在每座城市也设置了收费站。并且规定,车辆从一座城市到另一座城市的费用是,所经过公路费用和,加上所经过的城市中费用的次大值...(这里的次大可以和最大相同,
但是城市不同)。(这里的次大可以和最大相同,但是城市不同)。
现在 Mr_H 告诉你今年 k 次业务运送货物的起点、终点城市列表,请你帮忙计算,每次业务需要交纳的最低过路费。

输入格式

第一行包含三个用一个空格隔开的整数:n,m,k。其意义如题目描述。
第 2 到第 n+1 行:第 i+1 行包含一个单独的整数 c(1<=c<=100000),表示城市 i 的费用。
接下来的 m 行,每行包含三个整数 a,b,w,表示一条公路连接城市 a 和城市 b(1<=a,b<=n),其过路费为 w(1<=w<=100000)。
最后的 k 行,每行包含两个整数:s,t,表示一次业务的起点和终点(1<=s,t<=n 且 s!=t)。

输出格式

共 k 行,每行一个整数,表示从城市 s 到 t 的最少过路费。

输入样例

5 7 3
2
5
3
3
4
1 2 3
1 3 2
2 5 3
5 3 1
5 4 1
2 4 3
3 4 4
1 3
1 4
2 3

输出样例

4
7
8

提示

在这里插入图片描述

分析

在这里插入图片描述

我的做法(又双叒叕)和std差不多(有时候实现完全不同你也会说是差不多),然而我要回寝室了,所以……。

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

inline void Read(int &p)
{
	p=0;
	char c=getchar();
	while(c<'0' || c>'9') c=getchar();
	while(c>='0' && c<='9')
		p=p*10+c-'0',c=getchar();
}
 
const int MAXN=256;

bool vis[MAXN][2];
int n,m,q,u,v,w,arr[MAXN],G[MAXN][MAXN],ans[MAXN][MAXN],dis[MAXN][2];

struct node
{
	int pos,val,is;
	node(){}
	node(int p,int v,int i){pos=p,val=v,is=i;}
	bool operator < (const node &p) const
	{
		return val>p.val;
	}
}hep,to;
priority_queue<node>Q;

inline void del(node p) {if(dis[p.pos][p.is]>=p.val) dis[p.pos][p.is]=p.val,Q.push(p);}

void dijkstra(int sec)
{
	memset(vis,0,sizeof vis);
	memset(dis,0x3f,sizeof dis);
	del(node(sec,0,0));
	
	while(!Q.empty())
	{
		hep=Q.top(),Q.pop();
		if(vis[hep.pos][hep.is]) continue;
		vis[hep.pos][hep.is]=1;

		for(int i=1;i<=n;i++)
			if(G[hep.pos][i]!=0x3f3f3f3f)
			{
				to=node(i,dis[hep.pos][hep.is]+G[hep.pos][i],hep.is);
				if(arr[i]>arr[sec] && !hep.is) to.is=1,del(to);
				if(arr[i]==arr[sec]) {del(to); if(i!=sec) to.is=1,del(to);}
				if(arr[i]<arr[sec]) del(to);
			}
	}
}

int main()
{
    freopen("business.in","r",stdin);
    freopen("business.out","w",stdout);
	
	memset(G,0x3f,sizeof G),memset(ans,0x3f,sizeof ans);
	Read(n),Read(m),Read(q);
	for(int i=1;i<=n;i++) Read(arr[i]);
	for(int i=1;i<=m;i++) Read(u),Read(v),Read(w),G[u][v]=min(G[u][v],w),G[v][u]=min(G[v][u],w);
		
	for(int sec=1;sec<=n;sec++)
	{
		dijkstra(sec);
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				ans[i][j]=min(ans[i][j],min(dis[i][0]+dis[j][1],dis[i][1]+dis[j][0])+arr[sec]);
	}
	while(q--) Read(u),Read(v),printf("%d\n",ans[u][v]);
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值