【重庆市NOIP模拟赛】数据
来源:互联网 发布:阿里云服务器关闭快照 编辑:程序博客网 时间:2024/06/11 09:49
数据
时间限制: 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,线段树是最在行(难写)的了,(基本上就没写对过)。。。。鉴于本人目前对于线段树优化一知半解。。
这里。。。就不加解析了。。。等孤学成归来在好好补上吧。。这里就直接上代码了
#include<iostream>#include<cstring>#include<cstdio>#define LCH(i) (2 * i)#define RCH(i) (2 * i + 1)const int maxn = 100000;const int inf = 1 << 30;using namespace std;struct Tree{ int lc, rc, fgr[2];}tree[maxn * 4];int n, num[maxn + 5], pos[maxn + 5], f[maxn + 5];void built(int i, int l, int r){ tree[i].lc = l, tree[i].rc = r; tree[i].fgr[0] = tree[i].fgr[1] = inf; if(l == r){ pos[l] = i; return; } int mid = (l + r) / 2; built(LCH(i), l, mid); built(RCH(i), mid + 1, r);}void update(int i, int val, bool f){ tree[i].fgr[f] = min(tree[i].fgr[f], val); while(i != 1){ i /= 2; tree[i].fgr[f] = min(tree[i].fgr[f], val); }}int query(int i, int l, int r, bool f){ if(tree[i].lc > r || tree[i].rc < l) return inf; if(tree[i].lc >= l && tree[i].rc <= r) return tree[i].fgr[f]; return min(query(LCH(i), l, r, f), query(RCH(i), l, r, f));}int main(){ memset(f, 0x3f3f3f3f, sizeof f); scanf("%d", &n); built(1, 1, n + 1); for(int i = 1; i <= n; i ++) scanf("%d", &num[i]); num[++ n] = 0; f[n] = num[n]; update(pos[n], f[n] + n, 0); update(pos[n], f[n] - n, 1); for(int i = n - 1; i >= 1; i --){ int t = i + 1 + num[i]; if(t >= n){ t = tree[1].fgr[1]; f[i] = i + 1 + num[i] + t; update(pos[i], f[i] + i, 0); update(pos[i], f[i] - i, 1); continue; } t = query(1, i + 1 + num[i], n, 0); f[i] = min(f[i], t - (i + 1 + num[i])); t = query(1, i + 1, i + 1 + num[i], 1); f[i] = min(f[i], i + 1 + num[i] + t); update(pos[i], f[i] + i, 0); update(pos[i], f[i] - i, 1); } printf("%d", f[1]);}
Just do it!
- 【重庆市NOIP模拟赛】数据
- 【重庆市NOIP模拟赛】业务
- 20151006重庆市NOIP模拟赛总结
- 【总结】20151017重庆市NOIP模拟赛
- 【总结】20151024重庆市NOIP模拟赛
- 【总结】20151031重庆市NOIP模拟赛
- 【贪心】【枚举】【重庆市NOIP模拟赛】旅行
- CQBZOJ 【重庆市NOIP模拟赛】避难向导
- #bzoj2933#【重庆市NOIP模拟赛】数据(DP线段树优化 or DP堆优化 + 证明)
- #bzoj2934#【重庆市NOIP模拟赛】业务(SPFA / Dijk)
- #bzoj2932#【重庆市NOIP模拟赛】旅行(贪心 DP是不可以的!)
- NOIP模拟赛 baoj2933数据
- NOIP模拟赛题目以及数据
- noip模拟赛 双城记
- 【noip模拟赛】密码
- 10.10NOIP模拟赛
- 10.08NOIP模拟赛
- 10.11NOIP模拟赛
- Spring aop--几种不同的使用方式
- 布局引用引起错误
- java中的锁
- System.loadLibrary()流程分析
- poj-2115-循环次数
- 【重庆市NOIP模拟赛】数据
- 虚拟机上Linux的Tomcat8的配置
- Picasso,Glide,Fresco的前世今生
- 通过.frm和.ibd对mysql数据恢复
- tcp头部协议理解
- 微信小程序实例源码大全下载
- mfc textout显示字符串到窗口上
- wxWidgets+CodeBlocks Win平台搭建
- 关于python中中文字符切割乱码的问题