区间相关

求区间交和并

给定一些区间,求出这些区间的交集和并集。

  • 交集一定连续。我们维护左端点和右端点,按照任意顺序扫描并维护即可。

  • 并集,先按照左端点排序,然后扫过去,维护最大右端点,出现下一个左端点大于上一个右端点的时候,砍掉这个区间。

Pjudge NOIP Round #4

【题意】
虱子国王尼特这天有点不舒服,它周围的 \(n\) 个医生立刻开出了药方:第 \(i\) 个医生告诉它,从这天起的第 \(L_i\) 天到第 \(R_i\) 天,它应该服用 \(x_{i,1},x_{i,2},…,x_{i,K_i}\)\(K_i\) 种药,每天每种药应当服用恰好一片。注意,如果有多个医生的药方里都要求尼特在第 \(p\) 天服用第 \(q\) 种药,那尼特在第 \(p\) 天仍然只会服用一片第 \(q\) 种药。编号为 \(j\) 的药每片需要 \(c_j\) 元钱。

然而,由于尼特的疏忽,有恰好一位庸医混进了医生队伍里,但尼特并不知道哪位医生是庸医。所以它想知道,对于所有 \(1≤i≤n\),如果它按照除了第 \(i\) 个医生之外的所有医生的药方吃药,它总共将花费多少钱。

\(n,m \le 5\times 10^5,\sum K_i \le 10^6, c_i \le 10^6\)

【分析】
按照上次那道区间颜色的套路,我们首先求出所有医生的话都要听的时候的答案,然后对于每个医生考虑消除贡献。

“首先”这一步怎么做?对每一个颜色维护区间并,然后乘以权值即可。这部分代码:

f(i,1,m) {
        int sum = 0, l = 0, r = 0;
        sort(y[i].begin(),y[i].end(),cmp);
        for(pair<pii,int> j : y[i]) {
            if(j.fi.fi <= r) r = max(j.fi.se,r);
            else {sum+=(r==0?0:dis[r]-dis[l]); l=j.fi.fi,r=j.fi.se; }
        }
        sum+=(dis[r]-dis[l]);
        pans+=sum*c[i];
}

然后考虑维护一个颜色里有哪些是只有一个医生覆盖的区间。考虑扫描线并且维护一个 set,表示当前扫到的点里面有哪些医生。如果只有一个医生,那么整个区间都应该算贡献。这要怎么做比较好写?如果直接用 \(l\)\(r\) 处理,会遇到一些边界情况。其实考虑 \(r + 1\) 时刻,set 中才会真正删去一个点。这样把 \(r\)\(1\) 再处理会更好。

时间复杂度 \(O(n \log n)\)。这次还真过不去。

考虑卡常。把 set 换成 gp_hash_table。然后构造函数记得算时间复杂度,不要退化了。

然后过了。

#include<bits/stdc++.h>
#include<ext/pb_ds/tree_policy.hpp>
#include<ext/pb_ds/assoc_container.hpp>
using namespace __gnu_pbds;
using namespace std;
#define int long long
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9;
void cmax(int &x, int y) {if(x < y) x = y;}
void cmin(int &x, int y) {if(x > y) x = y;}
int dis[1000010], c[1000010], ans[1000010]; 
int n,m; int v;
struct doc{ 
    int l,r,k;
    vector<int> p;
}d[1000010];
int enc(int x) {return lower_bound(dis+1,dis+v+1,x)-dis;}
#define fi first 
#define se second
bool cmp(pair<pii,int> x,pair<pii,int> y){
    if(x.fi.fi!=y.fi.fi)return (x.fi.fi<y.fi.fi);
    return (x.fi.se<y.fi.se);
}
vector<pair<pii,int>>y[1000010];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    time_t start = clock();
    //think twice,code once.
    //think once,debug forever.
    cin>>n>>m;
    f(i,1,m)cin>>c[i];
    int cnt=0;
    f(i,1,n){
        cin>>d[i].l>>d[i].r>>d[i].k;
        d[i].r++;
        dis[++cnt]=d[i].l; dis[++cnt]=d[i].r;
        f(j,1,d[i].k) { int x; cin>>x; d[i].p.push_back(x); }
    }
    sort(dis+1,dis+cnt+1); v=unique(dis+1,dis+cnt+1)-dis-1;
    int pans = 0;
    f(i,1,n) for(int j : d[i].p) y[j].push_back({{enc(d[i].l),enc(d[i].r)},i});
    f(i,1,m) {
        int sum = 0, l = 0, r = 0;
        sort(y[i].begin(),y[i].end(),cmp);
        for(pair<pii,int> j : y[i]) {
            if(j.fi.fi <= r) r = max(j.fi.se,r);
            else {sum+=(r==0?0:dis[r]-dis[l]); l=j.fi.fi,r=j.fi.se; }
        }
        sum+=(dis[r]-dis[l]);
        pans+=sum*c[i];
	}
    f(i,1,n)ans[i]=pans;   
	int ccc = 0;
	gp_hash_table<int, null_type> stk;
	vector<vector<int>> jin(cnt+1); vector<vector<int>> chu(cnt+1);vector<int> vis;
    f(i,1,m){
        stk.clear(); vis.clear();
        for(pair<pii,int> j : y[i]) {
            vis.push_back(j.fi.fi); vis.push_back(j.fi.se);
        }
        for(int i : vis) {
        	jin[i].clear(); chu[i].clear();
		}
		for(pair<pii,int> j : y[i]) {
            jin[j.fi.fi].push_back(j.se); chu[j.fi.se].push_back(j.se); 
        } 
        sort(vis.begin(),vis.end());
        int ccnt = unique(vis.begin(),vis.end())-vis.begin()-1; 
		int lst = 0;  
        f(jj,0,ccnt){
        	ccc++; 
			int j=vis[jj];
            if(!chu[j].size()&&!jin[j].size())continue;
            if(stk.size() == 1) { 
				int dx = (*(stk.begin())); ans[dx]-=c[i]*max(0ll, dis[j]-1-lst + 1);
            }
            for(int k : jin[j]) { stk.insert(k); if(stk.size() == 1) lst = dis[j]; }
            for(int k : chu[j]) { stk.erase(k); if(stk.size() == 1) lst = dis[j]; }
        }
    }
    f(i,1,n)cout<<ans[i]<<" "; cout <<endl;
	time_t finish = clock();
   // cout << "time used:" << (finish-start) * 1.0 / CLOCKS_PER_SEC <<"s"<< endl;
    return 0;
}

区间定序贡献

为什么要对区间进行“左端点排序”/“右端点排序”/按照xxx顺序排序?

实际上是因为,这样排序能够对条件进行简化,以满足题目要求。如果从左往右扫,左端点排序能够让之前选到的任何区间的子集交/并的不会出现左端点大于当前区间左端点的情况。右端点排序能够让当前选到的区间的子集交/并的右端不会超出当前右端点。这都是简化区间之间关系的一种方式,和序列排序降维度的思想类似。

这里看一道题:

【题意】

给定若干个端点 \(a_i\),对每一个端点有一个长度 \(\ell_i\)。现在要给每一个端点定一个向左或者向右的方向,将其变成一条长度为 \(\ell_i\) 的线段。要求让线段的并长度尽可能大,输出长度。

【分析】

这里我们首先考虑的是新加入一个区间能够有什么效果。效果是,能够覆盖之前是空的的一些位置,对长度造成贡献。这个贡献,我们假设之前的区间右端点为 \(r\),那么贡献是右边这一段加上前面可能有的若干个空档。这个东西看着难以维护,但是如果有空档说明中间的一些区间是完全被包含的,也就是说它们实际上不产生任何贡献!于是,我们只需要计算右边那一段(得到的贡献不会更优),并且并不选择所有区间,而是跳过一些区间(使得右端点正常)。

这时候我们要注意一个问题,不能让某个新加入的区间右边有一个和它并没有交集的区间,否则就会出问题。这样的话我们容易考虑到这时候按照左/右端点排序都是不可以的,我们需要按照原先的中间端点排序,才能达到这个目的。

posted @ 2022-11-21 16:52  OIer某罗  阅读(38)  评论(0编辑  收藏  举报