[算进] 数据备份
数据备份
https://ac.nowcoder.com/acm/contest/1011/C
Problem
Sulotion
代码短,思维强,实现妙(就算猜出性质也不一定会实现),神仙题啊,科科(我太菜了而已)。
- 首先有一个显然的性质:选出来的这 对点一定相邻。
根据这个性质,我们做第一步问题转换:记两个点 和 之间的距离为 ,那么我们就得到一个长度为 的数组 ,我们要在 中选出 个数,并且这 个数两两不能相邻。
(做到这里就不会了是吧?)既然没有什么头绪,我们就从 的情况逐步推起,考虑像 [算进]序列 (就是上一题,虽然这两题看起来毫无联系的样子)一样用数学归纳法解决问题。
显然我们要选 ,并且记这个数为 。
显然决策集合只有两个部分:1.选 ( 是不与 相邻的最小值);2.选 。
如果你不理解的话,这里有解释:如果我们考虑选 或者 ,这就要使 换成别的数,假设将 换成 , (假设选 , 同理),那么 一定可以比它优。**于是我们可以不用管 与其他数组合能否得到最优解,因为它一定是不能的,只要考虑 和 能否组成最优解,因为这个在 里面没有考虑到。**
(k=3,它显得杂乱无章。所以我们不在乎数学的严谨性,直接讨论我们想看到的)
现在已经选出 的情况,假设如下图所示
令 中选出来的分别是 (和上图一样,他们 “间接相邻”)
那么决策集合也只有两个部分:1.选 ;2.选 ( 就是不干扰这两个点的最小的一个点)
直观地来说,要么选蓝色部分,要么选红色部分 +
如果你还是不理解的话,继续解释:如果我选了蓝色部分的一个点 (blue),那么就要干扰一个甚至多个点,***扰的点记为 (red),将它更换为其他的点 (else),因为其他部分没有变化,因为有 ,那么我们会发现这样选取是不可行的!如果干扰了多个点(最多为两个),记为 ,将它更换为其他的点 ,假设 因为 ,那么 ,如此一来,这不是说明 不是上一轮的最优解!?这与条件相矛盾,故不成立。
做出以上分析后,我们摸索出来一个性质:(先上一张图,假设当前选了 对点了)
如果有一些点 “间接相邻”,若有一个点***扰(选了一个蓝色点),所有的点改变位置(红色变蓝***域),形象地解释为:“同生死,共进退”。首先这个性质是我们通过刚才这些分析猜出来的,对于任意情况,可以用我给出来的这种方法做类似分析,这样证明是不是严谨的就不知道了,但是这个结论是可以证明的,具体我也不会、、
当然你还会想啊,我们选的点是离散的呀,比如下面这张图
但是对于间接相邻的这些点 上面这个性质成立呀!qwq
现在也许你对这个算法的思想大概明白了,却不知道怎么实现,这就要引出我们 “妙” 的代码实现了
开一个小根堆,结点存放 ,开一个链表。 表示这个结点在链表中相应的位置。
然而这个 就妙了,,,先讲实现流程:
每次从堆中取出一个结点,将 ,将链表中 中三个结点删去,加入在这个位置加入一个新的结点,这个新结点的 ,再把这个结点加入堆。结合“红蓝点的说法”,其实我们是把蓝点区域看成一种状态加入进了堆,val是记录红点区域和蓝点区域的差值(自己模拟一下会有一点点 容斥般巧妙的感觉qwq)。
重复 次得到答案。
还有 一点点细节,如果我们遇到下面这种情况:
红点在边缘,我把它叫做 “蓝点会溢出”,这种情况我们肯定不能再将蓝点区域放入堆里面了。反过来,我们不取出它就可以了。有一个小 ,将链表的头尾(虚拟)结点的 即可。
综上所述,这题考察了我们 优先队列+双向链表 的灵活运用。
Code
Talk is cheap.Show me the Code.
#include<bits/stdc++.h> #define int long long #define INF 1e18 using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); } return x * f; } const int N = 100007; int n,K,ans; int D[N],L[N],R[N],val[N]; bool vis[N]; priority_queue<pair<int,int> > q; signed main() { n = read(), K = read(); int last = read(); for(int i=2,x;i<=n;++i) x = read(), D[i-1] = x-last, last = x; for(int i=1;i<n;++i) val[i] = D[i], L[i] = i-1, R[i] = i+1, q.push(make_pair(-val[i],i)); val[0] = val[n] = INF; for(int i=1;i<=K;++i) { while(vis[q.top().second]) q.pop(); int x = -q.top().first, pos = q.top().second; q.pop(); //printf("x = %d\n",x); ans += x; int l = L[pos], r = R[pos]; L[pos] = L[l], R[pos] = R[r]; R[L[pos]] = pos, L[R[pos]] = pos; vis[l] = vis[r] = 1; val[pos] = val[l] + val[r] - x; //printf("updata x = %d\n",val[pos]); q.push(make_pair(-val[pos],pos)); } printf("%lld\n",ans); return 0; }
Summary
这是一道思维题,我还是要强行在这中间学到套路(逃:
善于从简单的情况入手,考虑数学归纳法,从而猜出一些性质
灵活运用双向链表,灵活实现代码(双向链表真是一种神奇的数据结构)