JOISC2017 切符の手配 / Arranging Tickets/ 门票安排
我们先尝试得到一个很好的多项式复杂度解法。我们浅浅分析一下性质,可以得到一个性质:我们先假设所有都是 \(x_i<y_i\),然后不翻转。然后我们去翻转。翻转的区间 \([x_i,y_i]\) 的交集 \([l,r]\) 一定是非空的!也就是一定存在一个 \(t\) 使得所有翻转的 \([l,r]\) 包含 \(t\)。很好理解,假如我们翻转了两个区间然后它们还没有共同交点,那么我们翻转后只不过是仅仅将所有位置都多覆盖了一次而已,不能节省任何东西,一定会使答案更劣。这启发我们在二分答案的时候去枚举 \(t\)。对于 \(i\le t\) 的位置 \([i,i+1]\),它被覆盖的次数为覆盖它区间数减去之前翻转的区间数加上之后翻转的区间数。我们考虑贪心。贪心的话我们必须要知道之后翻转的区间数。而如果我们知道总共需要翻转多少次就能求了!所以我们还要枚举一个 \(c\) 表示总共翻转多少个区间。设 \(a_i\) 表示它被多少个区间覆盖。设 \(s_p\) 表示有多少翻转的区间 \(x_i\le s_p\),不翻转时被覆盖的次数为 \(a_i\)。那么被覆盖的次数就是 \(a_i+c-2s_p\)!我们从左到右扫,然后当 \(a_i+c-2s_p>ans\) 时就需要翻转一些区间。翻转区间 \([x,y]\) 对后面的位置带来的代价是 \([y,n]\) 覆盖次数都要加一,\([x,y-1]\) 覆盖次数减一。那么显然 \(y\) 越大越好。所以我们就维护一个堆,维护 $x\le $ 目前的位置的所有合法区间,然后挑 \(y\) 最大的翻即可。如果 \(cnt\) 不够用了那么就不行,然后最后要把 \(cnt\) 给用完,然后 check 一下 \(t+1,\dots,n\) 的覆盖次数是否合法即可。
然后现在才只能做到 \(O(n^3\log^2 n)\)。
我们分析一些关于最优解的 \(ans,cnt,t\) 的性质。令 \(b\) 表示翻转后被覆盖次数。\([l,r]\) 表示翻转区间的交集。
Observation 1:对于所有 \(t\in[l,r-1]\),它们是等价的。所以我们考虑把 \(t\) 给换成 \([l,r-1]\) 中 \(b\) 最大的一个 \(t\)。由于 \([l,r]\) 中的 \(i\) 都满足 \(b_i=a_i-cnt\),于是换后 \(t\) 的 \(a\) 也会是 \([l,r-1]\) 中最大的。
Observation 2:\(b_t\ge \max b_i-1\)。如果不满足的话,我们跳出满足 \(x_u=l\) 的区间 \(u\) 和 \(y_v=r\) 的区间 \(v\)。我们把 \(u,v\) 都翻回去,那么 \(b_{[l,r-1]}\) 会 \(+2\),\(b_{[1,l-1]}\) 和 \(b_{[r,n]}\) 会 \(-2\),显然答案会更优。所以 \(b_t\ge \max b_i-1\)。有了这个 Observation,我们一定有 \(cnt=a_t-b_t=a_t-ans+1\) 或 \(a_t-ans\), 复杂度降到了 \(O(n^2\log^2 n)\)。
Observation 3:\(a_t\ge \max a_i\)。\(a_t=b_t+cnt\ge b_i+(cnt-1)\ge a_i\)。并且 \([l,r]\) 一定覆盖所有最大的 \(a\)。拿最左边的距离。假如不包含最左边的 \(t'\),那么 \(b_{t'}>a_{t'}-cnt=a_t-cnt=b_t\)。对右边的也是一样的。所以 \(t\) 随便取一个最大的 \(a\) 即可。复杂度降到了 \(O(n\log^2 n)\)。
#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define fi first
#define se second
#define eb emplace_back
#define popc __builtin_popcount
#define sgn(x) ((x)&1?-1:1)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef vector<pii> vp;
typedef unsigned long long ull;
typedef long double ld;
int read() {
int x=0,w=1; char c=getchar();
while(!isdigit(c)) {if(c=='-') w=-1; c=getchar();}
while(isdigit(c)) {x=x*10+c-'0'; c=getchar();}
return x*w;
}
const int N=4e5+9;
int n,m,a[N],b[N],c[N],d[N],tot;
vp f[N];
bool work(int t,int ans,int cnt) {
if(cnt<0) return 0;
int cc=0;
rep(i,0,n) d[i]=0;
priority_queue<pii>q;
rep(i,1,t) {
b[i]=a[i]+cnt-cc-cc;
for(pii r:f[i]) if(r.fi>=t) q.push(pii(r.fi,r.se));
while(b[i]>ans) {
if(!q.size()) return 0;
pii r=q.top(); q.pop();
int x=min(r.se,(b[i]-ans+1)/2);
d[t]-=x, d[r.fi]+=2*x, cc+=x, b[i]-=2*x;
if(x!=r.se) q.push(pii(r.fi,r.se-x));
if(cc>cnt) return 0;
}
}
while(cc<cnt&&q.size()) {
pii r=q.top(); q.pop();
int x=min(cnt-cc,r.se);
d[r.fi]-=x, d[r.fi]+=2*x, cc+=x;
}
rep(i,t+1,n) {
d[i]+=d[i-1];
if(a[i]+d[i]>ans) return 0;
} return 1;
}
signed main() {
n=read(), m=read();
rep(i,1,m) {
a[i]=read(), b[i]=read(), c[i]=read();
if(a[i]==b[i]) continue;
if(a[i]>b[i]) swap(a[i],b[i]);
f[a[i]].eb(pii(b[i],c[i])), d[a[i]]+=c[i], d[b[i]]-=c[i];
}
rep(i,1,n) a[i]=a[i-1]+d[i];
int t=1;
rep(i,1,n) if(a[i]>a[t]) t=i;
int l=0, r=a[t], ans=-1;
while(l<=r) {
int mid=l+r>>1;
if(work(t,mid,a[t]-mid)||work(t,mid,a[t]-mid+1)) ans=mid, r=mid-1;
else l=mid+1;
}
printf("%lld\n",ans);
return 0;
}