HNOI2013旅行
一道欺负我智商的题。。。
本来想打单调队列优化dp的,结果看到算法标签就点了此题
首先你要理解题意,蒟蒻理解了好久。它就是说,给你一个由1和-1组成的数列,让你分成m段,并让这m段区间和最大值最小,还要求多种方案时字典序最小。
我也不知道大佬怎么做的,反正我不会高斯消元。。。
哦,对了,如果输入的是0,表示他不喜欢则那一位为-1。
设总和为S。区间和最小值为ans。后缀和为sum[],后缀中0的个数为cnt[]。
为什么是后缀,往后看。。。
首先考虑特殊情况:
- 全是1 显然答案为ans=ceil(S/m);ceil()是向上取整。
- 全是-1 ans=ceil(abs(S)/m)
- 一半全1,一半全-1 比如11111-1-1-1可以变成11(111-1-1-1),括号里为0,可以与任意区间搭配,于是变成了上面的情况。
所以ans=ceil(abs(S)/m),简易证明:你可以用第三中方法狂消1和-1直到只有一种数,剩下来的数的个数是abs(S)。
如果abs(S)=0且能分的区间不足m那就另当别论。。。
由于我太菜了,还有情况没考虑就多多包含
先预处理sum[],ans,cnt[];
- S=0
- cnt[1]>=m,此时找sum[i]=0的点i,用单调队列维护找出字典序最小的一条即可。
- cnt[1]<m,ans>0,和下面一起处理。
- S!=0 sum的每一种取值分开考虑。
设上一个选的为last,则这一个i能选要满足abs(sum[last+1]-sum[i+1])<=ans,那么我们枚举sum[i+1]的取值时就可以直接从sum[last+1]-ans到sum[last+1]+ans。
并且abs(sum[i+1]/m'(即剩下要选的数量))要满足小于等于ans,i还有后面的数不能超过m'个。
然后跑单调队列就完啦,不要告诉我你切黑题还不会这个。。。
实现起来还有不少细节,比如负数下标之类的,仔细看下应该都能懂
如果你想TLE的话deque走起
#include<cstdio> #include<algorithm> const int N=5e5+5; int a[N],sum[N],cnt[N],tot; struct node { int l,r,val; }p[N<<1]; inline int newnode(int l,int r,int val) { p[++tot]=(node){l,r,val}; return tot; } struct que { int start,end,len; inline void push_back(int x) { if(!len)start=end=newnode(0,0,x); else p[end].r=newnode(end,0,x),end=p[end].r; ++len; } inline int empty(){return !len;} inline int front(){return p[start].val;} inline int back(){return p[end].val;} inline void pop_front(){start=p[start].r;--len;} inline void pop_back(){end=p[end].l;--len;} inline void push(int x) { while(!empty()&&a[back()]>a[x])pop_back(); push_back(x); } }dui[N<<1],dui1[N<<1],*q=dui+N,*q1=dui1+N; inline int min(const int &x,const int &y) {return a[x]<a[y]?x:y;} int main() { int n,m,ans;scanf("%d%d",&n,&m); for(int i=1;i<=n;++i)scanf("%d%d",&a[i],&sum[i]),sum[i]=sum[i]?1:-1; for(int i=n-1;i;--i)sum[i]+=sum[i+1]; for(int i=n;i;--i){cnt[i]+=cnt[i+1];if(!sum[i])++cnt[i];} cnt[n+1]=-1; // for(int i=1;i<=n+1;++i)printf("%d %d\n",sum[i],cnt[i]); int s=sum[1]; ans=s?(abs(s)-1)/m+1:cnt[1]<m;//printf("ss%d\n",ans); if(ans) { a[n+1]=n+1;int la=0; for(int i=2;i<=n;++i) q1[sum[i]].push_back(i-1); for(int i=1;i<m;++i) { int aa=n+1; for(int j=sum[la+1]-ans;j<=sum[la+1]+ans;++j) { if((abs(j)+m-i-1)/(m-i)>ans)continue; while(!q1[j].empty()&&n-q1[j].front()>=m-i){if(q1[j].front()>la)q[j].push(q1[j].front());q1[j].pop_front();} while(!q[j].empty()&&q[j].front()<=la){q[j].pop_front();} if(!q[j].empty())aa=min(aa,q[j].front()); } la=aa; printf("%d ",a[aa]); } } else { for(int i=1,j=2;i<m;++i) { for(;cnt[j+1]>=m-i;++j) if(!sum[j+1]) q[0].push(j); printf("%d ",a[q[0].front()]); q[0].pop_front(); } } printf("%d\n",a[n]); return 0; }