【区间/线段问题】

(贪心)【区间/线段问题】

模型整理

题目整理

注意不要推一些看上去简单又对的结论
数据范围小优先想想暴力+贪心

中场撸猫

https://ac.nowcoder.com/acm/contest/101921/D

正确结论

麻将塔的第一次层选择第一行最小的数字
之后每一层 除了最后一个 都选择这一层中不小于这一个点的右父亲的那个数字
(要遍历一遍上层数组->相当于暴力) 
最后一个选择不小于左父亲的即可
->记得特判!

错误结论

下一行二分查找大于等于上一行左端点的数即可
hack数据:
1 100 101 102
2 3 101 102
4 5 6 101
5 5 5 5

代码

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef pair<int,int> PII;
typedef long long ll;
ll abss(ll a){return a>0?a:-a;}
ll max_(ll a,ll b){return a>b?a:b;}
ll min_(ll a,ll b){return a<b?a:b;}
bool cmpll(ll a,ll b){return a>b;}
const int N=1e3+10;
int t;
int n;
int b[N][N];
void solve(){
    cin>>n;
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) cin>>b[i][j];
    for(int i=1;i<=n;i++) sort(b[i]+1,b[i]+1+n);
    //倾向于选小数
	int ans=1;
	vector<int> up;
	up.push_back(b[1][1]);
	bool is_ok=true; 
	for(int i=2;i<=n;i++){
		vector<int> tmp;
	 	int l=1,r=n+1;
	 	for(auto a : up){
	 		//二分查找
			r=n+1;
			while(l<r){
			 	int mid=(l+r)>>1;
			 	if(b[i][mid]>=a) r=mid;
			 	else l=mid+1;
			}
			if(l==n+1){
			 	is_ok=false;
			 	break;
		 	}
			else{
			 	tmp.push_back(b[i][l]);
			 	l++;
			}
		}
		//处理最后一个数
		r=n+1;
		while(l<r){
			int mid=(l+r)>>1;
			if(b[i][mid]>=up[up.size()-1]) r=mid;
			else l=mid+1;
		}
		if(l==n+1){
			break;
		}
		else{
			tmp.push_back(b[i][l]);
		} 
		if(!is_ok) break;
		up=tmp;
		ans++;
	}
	cout<<ans<<endl;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>t;
    while(t--) solve();
    return 0;
}

小紫的线段染色

https://ac.nowcoder.com/acm/contest/103948/E

思路

先考虑无解的情况:当一个点有三条及以上线段叠在一起时->差分求解
->※此时一定有解
按区间左端点排序 对重合进行反色处理->^操作
※※※注意存线段id->排序后会乱序 不能直接输出i !!!

代码

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef pair<int,int> PII;
typedef long long ll;
ll abss(ll a){return a>0?a:-a;}
ll max_(ll a,ll b){return a>b?a:b;}
ll min_(ll a,ll b){return a<b?a:b;}
bool cmpll(ll a,ll b){return a>b;}
const int N=1e5+10;
int t=1; 
int n;
struct node{
      int l,r,id;//记录id是为了记录第几条线段:注意线段不一定是有序的!
};
bool cmp(node x,node y){
      if(x.l!=y.l) return x.l<y.l;
      return x.r<y.r;
}
void solve(){
	cin>>n;
	vector<node> q(n+1);
	vector<PII> d;//1e9:离散化差分:直接推+排序
	for(int i=1;i<=n;i++){
            int l,r;
            cin>>l>>r;
            q[i].l=l;q[i].r=r;q[i].id=i;
            d.push_back({l,1});
            d.push_back({r+1,-1});
      }
      sort(d.begin(),d.end());
      int sum=0;
      bool is_ok=true;
      for(auto &[x,y]:d){
            sum+=y;
            if(sum>2){
                  is_ok=false;
                  break;
            }
      }
      if(!is_ok) cout<<"-1";
      else{
            sort(q.begin()+1,q.end(),cmp);
            vector<int> ans(n+1,0);
            int k=0;
            int r=-1,id=0;
            for(int i=1;i<=n;i++){
                  if(q[i].l>r){
                        r=q[i].r;
                        id=q[i].id;
                  }
                  else if(q[i].l<=r){
                        ans[q[i].id]=ans[id]^1;
                        k++;
                  }
            }
            if(k==0){//注意特判不需要染色的情况:至少染一个
                  cout<<1<<endl;
                  cout<<1;
            }
            else{
                  cout<<k<<endl;
                  for(int i=1;i<=n;i++){
                        if(ans[i]) cout<<i<<" ";
                  }
            }
      }
}
signed main(){
      ios::sync_with_stdio(0);
      cin.tie(0);
      cout.tie(0);
      //cin>>t;
      while(t--) solve();
      return 0;
}

Manhattan Pairs

https://codeforces.com/contest/2122/problem/C

题目大意

二维坐标系
n个点两两配对,求曼哈顿距离总和最大

思路

 1|2
-----
 3|4
如果把坐标分成4个象限:2和3配对 1和4配对是最优的
->以一个轴分两半,块内交叉即可

代码

const int N=3e5+10;
int n;
void solve(){
    cin>>n;
    vector<array<i64,3>> q(n+1);
    for(int i=1;i<=n;i++){
        int a,b;
        cin>>a>>b;
        q[i]={a,b,i};
    }
    sort(q.begin()+1,q.end(),[&](array<i64,3> x,array<i64,3> y)->bool{
        return x[0]<y[0];
    });
    vector<array<i64,3>> q1,q2;
    for(int i=1;i<=n/2;i++){
        q1.push_back(q[i]);
        q2.push_back(q[n-i+1]);
    }
    sort(q1.begin(),q1.end(),[&](array<i64,3> x,array<i64,3> y)->bool{
        return x[1]<y[1];
    });
    sort(q2.begin(),q2.end(),[&](array<i64,3> x,array<i64,3> y)->bool{
        return x[1]>y[1];
    });
    for(int i=0;i<n/2;i++){
        cout<<q1[i][2]<<" "<<q2[i][2]<<endl;
    }
}

【区间和点匹配】Canvas Painting

https://qoj.ac/contest/2513/problem/14303

题目大意

QQ_1757349888912

思路

(1)连边肯定在相邻两点之间最优
->把连边当成位置,有\(n-1\)个连边,把区间右端点-1
->转化为区间和点匹配的问题
(2)从点的角度考虑:从左到右考虑每个点
选择所有覆盖它的区间中右端点最小的进行匹配一定最优
(3)从区间的角度考虑:把所有区间按照右端点排序再依次考虑每个区间,每次选择该区间内部最靠左且没匹配的点即可

代码

int n,m;
void solve(){
    cin>>m>>n;
    vector<PII> a(m+1);
    for(int i=1;i<=m;i++){
        cin>>a[i].fi>>a[i].sc;
    }
    sort(a.begin()+1,a.end(),[&](PII x,PII y)->bool{
        if(x.sc!=y.sc) return x.sc<y.sc;
        return x.fi<y.fi;
    });
    //记得特判1!
    if(n==1){
        cout<<1<<endl;
        return;
    }
    set<PII> pos;pos.insert({1,n-1}); //当前尚未被打通的 “连续连接点区间”
    int ans=n;
    //优先打通最靠左的点
    for(int i=1;i<=m;i++){
        auto [l,r]=a[i];
        r--;
        if(l>r) continue;
        auto it=pos.upper_bound({l,n});
        //优先找<=l的最大
        bool st=0;
        if(it!=pos.begin()){
            //若 tmp 包含 l,说明 l 是当前魔法区间 [l, r] 内最靠左的可用连接点
            auto tmp=it;tmp--;
            if(tmp->fi<=l && l<=tmp->sc){
                ans--;
                st=1;
                int L=tmp->fi,R=tmp->sc;
                pos.erase(tmp); //l点被覆盖了,拆成两个区间
                //注意判断区间有效!因为有r-1
                if(L<=l-1) pos.insert({L,l-1});
                if(l+1<=R) pos.insert({l+1,R});
            }
        }
        if(st) continue;
        //然后考虑>l的交集
        if(it!=pos.end()){ //能找到且为交集
            int L=it->fi,R=it->sc;
            if(L>r) continue;
            //打通当前区间最靠左的连接点L
            pos.erase(it);
            ans--;
            if(L+1<=R) pos.insert({L+1,R});
        }
    }
    cout<<ans<<endl;
}
posted @ 2025-02-18 11:48  White_ink  阅读(9)  评论(0)    收藏  举报