bzoj3141: [Hnoi2013]旅行
Description
Input
第 一行为两个空格隔开的正整数n, m,表示旅行的城市数与旅行所花的月数。接下来n行,其中第 i行包含两个空格隔开的整数Ai和Bi,Ai表示他第i个去的城市编号。Bi为0或1;如果 Bi=0则表示城市Ai没有小L想去的景点,如果Bi=1则表示城市Ai有小L想去的景点,
Ai两两不同且有1<=Ai<=N,即{Ai}为1,2....N的一个排列。
例如{2,1,3,4...N}
N<=500000,M<=200000
Output
t仅包括一行,包含m个空格隔开的正整数X1,X2...Xm,t仅包括一行,包含m个空格隔开的正整数X1,X2...Xm,为给小L安排的旅行计划对应的路线。为给小L安排的旅行计划对应的路线。
Sample Input
2 0
3 1
4 1
1 0
5 0
6 1
7 1
8 0
Sample Output
HINT
第1个月得到2点快乐值与2点疲劳值,第2个月得到1点快乐值与1点疲劳值,第3 个月得到1点快乐值与1点疲劳值。3个月中疲劳值与快乐值差的最大值为0,达到所有方案最小值。
可行方案有:
1 6 8
3 6 8
3 1 8
其中1 6 8字典序最小。
题解:
数组b[i]表示城市a[i]是否为有小L想去的景点,1表示有,-1表示没有
记sum[i]=b[i+1]+b[i+2]+...+b[n],S=sum[0],最大值最小为d,则
if S==0
if sum[i]==0 的个数 >= m 则将这些等于0的数做一遍单调队列
else d=1
else d=ceil(abs(S)/m)
证明见http://cxjyxx.me/?p=329
然后就是如何求字典序最小
我们每次要找到一个 能使后面的数仍然有解的 最小的一个数作为结尾
设这个新区间是第i个区间,结束点为k
首先这个新区间要合法,则abs(S-sum[k])/(m-i)<=d
然后要保证后面要有解,则ceil(abs(sum[k])/(m-i))<=d
然后用单调队列维护每种sum值的最小值
具体见代码
code:
1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 #include<cstring> 5 #include<algorithm> 6 #define maxn 500005 7 #define base maxn 8 using namespace std; 9 char ch; 10 bool ok; 11 void read(int &x){ 12 for (ok=0,ch=getchar();!isdigit(ch);ch=getchar()) if (ch=='-') ok=1; 13 for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar()); 14 if (ok) x=-x; 15 } 16 struct Point{int v,id;}; 17 int cnt; 18 struct node{ 19 Point p; 20 int pre,next; 21 }T[maxn<<1]; 22 int newnode(Point p,int pre,int next){T[++cnt]=(node){p,pre,next};return cnt;} 23 struct queue{ 24 int siz,head,tail; 25 bool empty(){return !siz;} 26 void push_front(Point p){ 27 if (!siz) head=tail=newnode(p,0,0); 28 else T[head].pre=newnode(p,0,head),head=T[head].pre; 29 ++siz; 30 } 31 void push_back(Point p){ 32 if (!siz) head=tail=newnode(p,0,0); 33 else T[tail].next=newnode(p,tail,0),tail=T[tail].next; 34 ++siz; 35 } 36 void pop_front(){siz--,head=T[head].next;} 37 void pop_back(){siz--,tail=T[tail].pre;} 38 Point front(){return T[head].p;} 39 Point back(){return T[tail].p;} 40 }list[maxn<<1]; 41 int tot,now[maxn<<1],pre[maxn<<1]; 42 Point point[maxn<<1]; 43 int n,m,last,val[maxn],sum[maxn],rest[maxn]; 44 void add(int u,Point p){pre[++tot]=now[u],now[u]=tot,point[tot]=p;} 45 int calc(){ 46 int ans=0; 47 for (int i=n;i>=1;i--){ 48 if (!sum[i]) ans++,rest[i]=1; 49 rest[i]+=rest[i+1]; 50 } 51 if (ans>=m) return 0; else return 1; 52 } 53 void push(int u,Point p){ 54 while (!list[u].empty()&&p.v<list[u].back().v) list[u].pop_back(); 55 list[u].push_back(p); 56 } 57 Point calc(int u,int lim){ 58 for (int p=now[u];p&&point[p].id<=lim;p=pre[p]) push(u,point[p]),now[u]=p; 59 while (!list[u].empty()&&list[u].front().id<=last) list[u].pop_front(); 60 return list[u].empty()?(Point){maxn,maxn}:list[u].front(); 61 } 62 Point min(Point a,Point b){return a.v<b.v?a:b;} 63 int main(){ 64 read(n),read(m); 65 for (int i=1;i<=n;i++) read(val[i]),read(sum[i-1]),sum[i-1]=(sum[i-1])?1:-1; 66 for (int i=n-1;i>=0;i--) sum[i]+=sum[i+1]; 67 for (int i=n;i>=1;i--) add(sum[i]+base,(Point){val[i],i}); 68 int S=sum[0],d=(S!=0)?(int)ceil(1.0*abs(S)/(1.0*m)):calc(); 69 if (d){ 70 for (int i=1;i<m;i++){ 71 Point ans=(Point){maxn,maxn}; 72 for (int j=S-d+base;j<=S+d+base;j++){ 73 if (ceil(abs(1.0*j-base)/(1.0*m-i))<=d) 74 ans=min(ans,calc(j,n-(m-i))); 75 } 76 printf("%d ",ans.v); 77 last=ans.id,S=sum[last]; 78 } 79 printf("%d\n",val[n]); 80 } 81 else{ 82 for (int i=1,p=now[base];i<m;i++){ 83 for (;p&&rest[point[p].id]-1>=m-i;p=pre[p]) push(base,point[p]); 84 printf("%d ",list[base].front().v); 85 list[base].pop_front(); 86 } 87 printf("%d\n",val[n]); 88 } 89 return 0; 90 }