【重庆市NOIP模拟赛】数据

数据
时间限制: 1 Sec 内存限制: 128 MB
提交: 58 解决: 31
[提交][状态][我的提交]
题目描述
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。

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

样例输入
Copy (如果复制到控制台无换行,可以先粘贴到文本编辑器,再复制)

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+a[i]+1个点,所以连一条权值为零的单向边。
  • 同时,每个点可以通过加减来左移或右移,而每移动一次就是修改一次,所以从每个点向两边相邻的点连一条权值为一的边。
  • 最后答案就是从1到n+1的最短路径。

注意三点:

  1. 点1如果为负数要先把它变为0再连边
  2. 点1不向两边连,点2不向左连
  3. 连向的点可能超过n+1,也是合法的
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int n,a,nn,dis[110005],u,v,ans;
queue<int>q;
bool inq[110005];
int fir[110005],nxt[660010],to[660010],w[660010],tot;
void line(int x,int y,int z)
{
	nxt[++tot]=fir[x];
	fir[x]=tot;
	to[tot]=y;
	w[tot]=z;
}
void spfa()
{
	memset(dis,0x3f,sizeof dis);
	dis[1]=0;
	q.push(1);
	while(!q.empty())
	{
		u=q.front();
		for(int i=fir[u];i;i=nxt[i])
		{
			v=to[i];
			if(dis[v]>dis[u]+w[i])
			{
				dis[v]=dis[u]+w[i];
				if(!inq[v]) inq[v]=1,q.push(v);
			}
		}
		inq[u]=0;
		q.pop();
	}
}
int main()
{
	scanf("%d",&n);
	scanf("%d",&a);
	if(a<0) ans=-a,line(1,2,0);
	else line(1,a+2,0);
	nn=max(nn,a+2);
	for(int i=2;i<=n;i++)
	{
		scanf("%d",&a);
		if(a>=0) line(i,i+a+1,0);
		nn=max(nn,i+a+1);
		line(i,i+1,1);
		line(i+1,i,1);
	}
	for(int i=n+1;i<nn;i++) line(i,i+1,1),line(i+1,i,1);
	spfa();
	printf("%d",dis[n+1]+ans);
}

法二 DP的(优先队列)堆优化

设f[i]表示把前i个数合法化需要的最小操作次数,则:
f[i]=f[j]+abs(a[j+1]-(i-j-1));{0<=j< i, 1<=i<=n;}

这里的abs(a[j+1]+j+1-i)便是整个题最磨人的地方了。

尽管我们很容易想到分类讨论,但却发现两个严肃的问题:

  • a[j+1]可能为负数,所以a[j+1]+j+1并不具有严格的增减性,不能使用单调队列优化

  • 当i< a[j+1]+j+1时, 我们不知道什么时候该pop,因为i在递增,此时不满足的a[j+1]+j+1可能以后就满足了。

so,我们采用了一种巧妙的优化。见代码:

#include<cstdio>
#include<queue>
#include<cctype>
using namespace std;
struct node
{
	int x,y;
	node(){}
	node(int a,int b){x=a;y=b;}
	bool operator < (const node& p)const{return x>p.x;}
};
priority_queue<node>q;
int n,a[100005],minx=10000000,f[100005];
inline void get(int &a)
{
	char c;a=0;int f=1;
	while(!isdigit(c=getchar())) if(c=='-') f=-1;
	while(isdigit(c)) a=a*10+c-'0',c=getchar();
	a*=f;
}
int main()
{
	get(n);
	for(int i=1;i<=n;i++) get(a[i]);
	q.push(node(a[1]+1,a[1]+1));
	for(int i=1;i<=n;i++)
	{
		while(!q.empty()&&q.top().y<=i)
		{
			minx=min(q.top().x-2*q.top().y,minx);
			q.pop();
		}
		f[i]=minx+i;
		if(!q.empty()) f[i]=min(f[i],q.top().x-i);
		q.push(node(f[i]+a[i+1]+i+1,a[i+1]+i+1));
	}
	printf("%d",f[n]);
}

pay attention to the rule!!!
这个优先队列并不是按照a[i+1]+i+1来排序的,而是f[i]+a[i+1]+i+1!!!
首先,对于a[j+1]+j+1<=i的情况,因为前面的i满足,后面的i肯定也满足,所以直接用一个minx来储存,然后直接pop。
而对于a[j+1]+j+1>i的情况,直接取最优的f[j]+a[j+1]+j+1则可。

但是,同志们可能会发现一个问题,既然是按照f[i]+a[i+1]+i+1排序,
那么如果在a[j+1]+j+1>i时直接退出,那么如果后面还有满足a[j+1]+j+1<=i的值怎么办呢?

这就是巧妙之处了。

令f[j]=a,a[j+1]+j+1=b;当b>i时记作a1,b1;当b<=i时记作a2,b2
那么我们的f[i]=min(a2-b2+i,a1+b1-i);
这里我们取了最优的a1+b1-i,那么我们如何说明后面的a2-b2+i不会比现在更优呢?
注意优先队列的规则,我们得知:a1+b1<=a2+b2
而我们已知:b2<=i
所以 2 * b2 <= 2 * i
所以 b2 <= 2*i-b2
所以 a2+b2<=a2+2*i-b2
所以 a1+b1<=a2+b2<=a2-b2+2*i
所以 a1+b1-i<=a2-b2+i !!!
证明至此,我们可知后面的解不会比当前更优,直接退出。


法三:DP的线段树优化

众所周知,对于这种含绝对值的DP,线段树是最在行(难写)的了,(基本上就没写对过)。。。。鉴于本人目前对于线段树优化一知半解。。
这里。。。就不加解析了。。。等孤学成归来在好好补上吧。。这里就直接上代码了

于是过了两年我学成归来了,做到原题根本没有想到上面两种做法直接开打线段树优化。。
f [ i ] = f [ j ] + a b s ( a [ j + 1 ] − ( i − j − 1 ) ) ; 0 < = j < i , 1 < = i < = n ; f[i]=f[j]+abs(a[j+1]-(i-j-1));{0<=j< i, 1<=i<=n;} f[i]=f[j]+abs(a[j+1](ij1));0<=j<i,1<=i<=n;
根据 i i i可以得到abs取正负时 j j j的范围,可以看出时两个区间,线段树维护 f [ j ] + a [ j + 1 ] + j + 1 f[j]+a[j+1]+j+1 f[j]+a[j+1]+j+1 f [ j ] − ( a [ j + 1 ] + j + 1 ) f[j]-(a[j+1]+j+1) f[j](a[j+1]+j+1)的最小值即可。

这个代码是从后往前做的,大致思路相同。

#include<bits/stdc++.h>
#define maxn 100005
using namespace std;
const int inf = 1<<30;
int n,a[maxn],f[maxn];
struct Segment_Tree{
	int mn[maxn<<2];
	void insert(int i,int l,int r,int x,int v){
		if(l==r) {mn[i]=v;return;}//special
		int mid=(l+r)>>1;
		if(x<=mid) insert(i<<1,l,mid,x,v);
		else insert(i<<1|1,mid+1,r,x,v);
		mn[i]=min(mn[i<<1],mn[i<<1|1]);
	}
	int query(int i,int l,int r,int x,int y){
		if(x<=l&&r<=y) return mn[i];
		int mid=(l+r)>>1,ret=inf;
		if(x<=mid) ret=min(ret,query(i<<1,l,mid,x,y));
		if(y>mid) ret=min(ret,query(i<<1|1,mid+1,r,x,y));
		return ret;
	}
	void ins(int x,int v){insert(1,1,n+1,x,v);}
	int ask(int l,int r){return query(1,1,n+1,l,r);}
}t1,t2;
int main()
{
	freopen("data.in","r",stdin);
	freopen("data.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	t1.ins(n+1,-(n+1)),t2.ins(n+1,n+1);
	for(int i=n;i>=1;i--){
		f[i]=inf;
		if(a[i]>=0) f[i]=min(f[i],t1.ask(i+1,min(i+a[i]+1,n+1))+i+a[i]+1);
		if(i+a[i]+1<=n) f[i]=min(f[i],t2.ask(max(i+a[i]+2,i+1),n+1)-(i+a[i]+1));
		t1.ins(i,f[i]-i),t2.ins(i,f[i]+i);
	}
	printf("%d\n",f[1]);
}

Just do it!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NOI(全国青少年信息学奥林匹克竞)模拟的测试数据是指用于评测参选手的程序的输入和对应的输出。测试数据是非常重要的,因为它决定了参选手的程序能否正确地解决问题。 在NOI模拟中,测试数据具有以下特点: 1.充分覆盖:测试数据应涵盖各种可能的输入情况,包括边界条件和极端情况。通过提供不同的测试数据,可以考察选手对问题的全面理解和解决能力。 2.随机性和均衡性:为了公平起见,测试数据应该是随机生成的,而不是针对某个特定算法或解法设计的。同时,测试数据应该是均衡的,即各种情况的概率应该大致相等,以避免偏向某些解法。 3.合理性和可行性:测试数据应该是合理和可行的,即符合题目要求的输入数据,并且是选手能够通过编写程序来处理的。测试数据应该考虑到程序的限制和时间复杂度,以充分测试选手的编程能力。 NOI模拟的测试数据通常由经验丰富的考题组负责生成。他们会根据题目的要求和限制,设计出一组合理、充分、随机和均衡的测试数据,以确保参选手的程序在各种情况下都能正确运行,并且能通过性能测试。 总之,测试数据在NOI模拟中起到了至关重要的作用,它既考察了选手对问题的理解和解决能力,又提高了选手编程的技巧和效率。同时,合理和恰当的测试数据也是公平竞的保证,确保每个参选手有相同的机会和条件进行竞争。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值