数据(图论)

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。
 
【输出格式】
 
  一个整数,表示把数据修改为合法的情况下,最少需要多少步。
 
【输入样例】
 
【样例1】
 4
 1 9 3 2
【样例2】
 10
 4 4 3 5 0 -4 -2 -1 3 5 
 
【输出样例】
 
【样例1】
 2


【样例1】
 3
 
【数据范围】
 
对于20%的数据,m<=10,  |a[i]|<=5;
对于60%的数据,m<=5000, |a[i]|<=10000

对于100%的数据,m<=100000, |a[i]|<=10000


分析:

1.当a[i]>=0,i号节点可以不加修改地到达i+a[i]+1号节点,于是连一条从i出发的权值为0的有向边,注意i+a[i]+1可以大于m+1;这一步当a[i]<0时可以直接忽略(思考为什么)

2.任意一个节点向左或向右一步都相当于是一次操作,于是左右各连一条权值为1的无向边;这一步当a[i]<0时不能忽略(思考为什么)

3.a[1]不能小于0。所以当a[1]小于0时,答案直接加上-a[1]并把a[i]变为0,继续后面的操作(思考为什么变成0就可以了)

4.a[1]和a[2]之间一定不能连无向边(即因为操作而连的边),举几个例子就知道了

5.注意细节:因为可能有些点连到了大于m+1的点,m+1可能通过这些大于它的点到达,所以大于m+1的点也需要左右连一条权值为1的无向边

6.当边连好之后,求出1节点到m+1节点的最短路就可以了


#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
const int maxm=100005;
const int INF=2000000000;
int m,last[maxm*2],np=0;
struct edge{int to,pre,w;}E[maxm*5];
struct data
{
	int id,d;
	friend bool operator < (data a,data b) {return a.d>b.d;}
};

char c;int flag;
inline void scan(int &x)
{
	flag=1;
	while((c<'0'||c>'9')&&c!='-') c=getchar();
	if(c=='-') flag=-1,c=getchar();
	for(x=0;c>='0'&&c<='9';c=getchar()) x=x*10+c-'0';
	x*=flag; 
}

inline void addedge(int u,int v,int w)
{
	E[++np]=(edge){v,last[u],w};
	last[u]=np;
}

int dis[maxm*2],done[maxm*2];
inline int Dijkstra()
{
	priority_queue<data>pq;
	for(int i=0;i<=maxm+20000;i++) dis[i]=INF;
	dis[1]=0;
	pq.push((data){1,0});
	while(!pq.empty())
	{
		data t=pq.top();
		pq.pop();
		int i=t.id,d=t.d;
		if(done[i]) continue;
		done[i]=1;
		for(int p=last[i];p;p=E[p].pre)
		{
			int j=E[p].to,w=E[p].w;
			if(dis[j]>dis[i]+w)
			{
				dis[j]=dis[i]+w;
				pq.push((data){j,dis[j]});
			}
		}
	}
	return dis[m+1];
} 

int main()
{
//	freopen("in.txt","r",stdin);
	scan(m);
	int k=0,x;
	scan(x);
	if(x<0) addedge(1,2,0),k-=x;//判断a[1]的正负 
	else addedge(1,x+2,0);
	for(int i=2;i<=m;i++)
	{
		scan(x);
		if(x>=0) addedge(i,i+x+1,0);//不加修改地到达 
		addedge(i+1,i,1);//左右修改一次 
		addedge(i,i+1,1);
	}
	for(int i=m+1;i<=m+20000;i++) addedge(i,i+1,1),addedge(i+1,i,1);//大于m+1的点 
	int ans=k+Dijkstra();
	printf("%d",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/wwwengine/article/details/80792238