专栏文章
P14385 [JOISC 2017] 门票安排 / Arranging Tickets 题解
P14385题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mind3e6b
- 此快照首次捕获于
- 2025/12/02 00:26 3 个月前
- 此快照最后确认于
- 2025/12/02 00:26 3 个月前
神仙性质题,不愧是 JOISC。
显然,我们可以将题目转化为给定 条线段,可以将若干条线段取其补集,求 的最小值。
我们定义对一个线段进行翻转表示取该线段的补集。
subtask 1,2
性质1:答案满足单调性。
这说明我们可以用二分答案解决问题。
性质2:对于一个翻转线段的集合 ,其中所有线段的交集一定不为空。证明:考虑反证法,假设存在两条线段 满足 ,将其翻转后对于 的 不变,其余的加 2,显然一定不优。
也就是说我们可以尝试找出一个 满足翻转区间集 的交集包含 。
考虑二分一个 ,接下来考虑如何 check。
我们假设 表示所有线段均不翻转时的 , 表示翻转线段集合 后的 , 表示只统计 中线段的 ,此时则有 ,显然
所以说如果说我们能确定 和 ,就能确定 的下线和枚举集合 的大小。
接下来我们就把问题转化为了给定 和 ,判断是否满足
考虑枚举 ,依次将 且 的加入堆,再贪心选择 最大的线段加入直到满足条件,如果做不到就不合法。
注意,此时的 ,不然 , 并不合法。
这一部分的代码如下:
C#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=200005;
const int INF=1e15;
struct node{
int l,r,v;
}a[N];
bool operator <(node a,node b){
return a.r<b.r;
}
vector<node>v[N];
int n,m;
int p[N],z[N];
bool chk(int t,int zt,int T){
for(int i=t+1;i<=n;i++)z[i]=0;
priority_queue<node>q;
int sum=0;
for(int i=1;i<=t;i++){
int lim=(p[i]+zt-T+1)/2;
for(node j:v[i])if(j.r>t)q.push(j);
while(sum<lim&&!q.empty()){
node u=q.top();
q.pop();
int date=min(lim-sum,u.v);
sum+=date;
z[u.r]-=date;
u.v-=date;
if(u.v)q.push(u);
}
if(sum<lim)return 0;
}
z[t]=sum;
for(int i=t+1;i<=n;i++){
z[i]+=z[i-1];
if(z[i]*2<p[i]+zt-T)return 0;
}
return 1;
}
bool check(int mid){
int mx=0;
for(int i=1;i<=n;i++)mx=max(mx,p[i]-mid);
for(int t=1;t<=n;t++)
for(int zt=mx;zt<=p[t];zt++){
if(chk(t,zt,mid)){
return 1;
}
}
return 0;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a[i].l>>a[i].r>>a[i].v;
if(a[i].l>a[i].r)swap(a[i].l,a[i].r);
v[a[i].l].push_back(a[i]);
p[a[i].l]+=a[i].v;
p[a[i].r]-=a[i].v;
}
for(int i=1;i<=n;i++)p[i]+=p[i-1];
int l=0,r=INF,ans=0;
while(l<r){
int mid=(l+r)>>1;
if(check(mid))r=mid,ans=mid;
else l=mid+1;
}
cout<<ans<<"\n";
}
subtask 3
显然,目前的瓶颈在于暴力枚举 和 ,考虑挖掘更多性质进行优化。
性质3:对于目前 所有线段的并 ,最优方案一定满足 。证明:考虑反证法,我们假设存在一种方案满足 。首先这种方案中 一定不包含线段 ,继续考虑反证法。我们假设存在一个线段 ,考虑翻转该线段。
由 可得出 ,显然翻转 更优。现在我们来考虑以下调整方案:
- 如果 或 ,停止操作。
- 我们选择一个左端点为 和一个右端点为 的两个区间,将它们移除 ,此时对于 的 不变,对于 变为 ,接着回到操作 1。
显然,进行调整后的 ,得证。
于是我们只需判断 和 两种情况即可。
这一部分的 check 如下:
Cbool check(int mid){
int mx=-INF;
for(int i=1;i<=n;i++)mx=max(mx,p[i]-mid);
for(int i=1;i<=n;i++){
bool ans=0;
if(p[i]-mid>=mx)ans|=chk(i,p[i]-mid,mid);
if(p[i]-mid+1>=mx)ans|=chk(i,p[i]-mid+1,mid);
if(ans)return 1;
}
return 0;
}
subtask 4,5
此时我们的复杂度瓶颈来到了枚举 上面。
性质 4:设 ,如果说最优方案满足 ,此时 同时满足证明:还是考虑反证法,我们假设存在最优方案 满足 且 ,设 。显然 ,因此,至少有一个线段没有包含 即 ,于是我们可以得出再联立之前的式子 可以得出不等式与原假设不符,得证。
于是我们将 的范围缩小到了 的范围内。
性质 5:若一种方案满足 ,那么 。
证明:类似性质 4 的反证,我们设 ,显然 ,于是我们就能得到以下不等式:与原假设不符,得证。
于是我们将时间复杂度优化到了 ,足以通过本题。
CPP#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=200005;
const int INF=1e15;
struct node{
int l,r,v;
}a[N];
bool operator <(node a,node b){
return a.r<b.r;
}
vector<node>v[N];
int n,m;
int p[N],z[N];
bool chk(int t,int zt,int T){
// cout<<"sb\n";
for(int i=t+1;i<=n;i++)z[i]=0;
priority_queue<node>q;
int sum=0;
for(int i=1;i<=t;i++){
int lim=(p[i]+zt-T+1)/2;
for(node j:v[i])if(j.r>t)q.push(j);
while(sum<lim&&!q.empty()){
node u=q.top();
q.pop();
int date=min(lim-sum,u.v);
sum+=date;
z[u.r]-=date;
u.v-=date;
if(u.v)q.push(u);
}
if(sum<lim)return 0;
}
z[t]=sum;
for(int i=t+1;i<=n;i++){
z[i]+=z[i-1];
if(z[i]*2<p[i]+zt-T)return 0;
}
return 1;
}
bool check(int mid){
int mx=-INF;
for(int i=1;i<=n;i++)mx=max(mx,p[i]-mid);
for(int i=1;i<=n;i++){
bool ans=0;
if(p[i]-mid==mx){
return chk(i,p[i]-mid,mid)|chk(i,p[i]-mid+1,mid);
}
}
return 0;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a[i].l>>a[i].r>>a[i].v;
if(a[i].l>a[i].r)swap(a[i].l,a[i].r);
v[a[i].l].push_back(a[i]);
p[a[i].l]+=a[i].v;
p[a[i].r]-=a[i].v;
}
int res=0;
for(int i=1;i<=n;i++)p[i]+=p[i-1],res=max(res,p[i]);
int l=0,r=INF,ans=0;
while(l<r){
int mid=(l+r)>>1;
if(check(mid))r=mid,ans=mid;
else l=mid+1;
}
cout<<min(ans,res)<<"\n";
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...