题解:P9247 [集训队互测 2018] 完美的队列

思路:

@yuruilin2026 和@Hootime 两个神犇认为这是道水题并且三分钟切了
看了时间以后直接考虑根号,时间复杂度就为一般的 \(O(n\sqrt{n\log n})\)
这道题突破点就在求每个 \(x\) 的贡献,不难看出,每个 \(x\) 的贡献取决于它在队列中存在的时间。具体的,记 \(x\) 的加入时间为 \(x_{in}\)\([l,r]\) 队列中最后一个 \(x\) 弹出的时间为 \(x_{out}\) ,那么这个 \(x\) 的贡献区间为 \([x_{in},x_{out}-1]\)
那么现在我们就需要求出最小的 \(x_{out}\) 。从第 \(x_{in}\) 刻开始,\([l,r]\) 中的每个位置 \(s\) 都至少被覆盖了 \(A_s\) 次(这样的话,就会把这次加入的 \(x\) 弹出)。现在开始考虑分块,每个块预处理为 \(f_{block,i}\) ,表示第 \(block\) 块中从 \(x_{in}\) 开始后最小的 \(x_{out}\) 。同样,每个块需要支持区间加和区间查询最值。
所以,对于每种颜色,我们对它的贡献区间取个并集,就能得到它对哪些时刻的答案有影响,而贡献区间总数是 \(O(n)\) 的,所以这部分是容易做的。
然后就是处理块,每个块的开头和结尾定义为 \(block_{start}\)\(block_{end}\) ,若查询的 \([l,r]\)\(l=block_{start},r=block_{end}\) 那么直接使用预处理的值,如果不是,那么就要处理其他散块的贡献,我们需要回答 \(O(m\sqrt n)\) 个询问,每个询问都是从第 \(x_{in}\) 时刻开始,位置 \(x\) 最小的 \(x_{out}\),使得在 \([x_{in},x_{out}]\)\(x\) 被覆盖了至少 \(A_x\) 次。发现这里有序列维和时间维,考虑离线扫描线,维护每一个 \(x\) 在时间维上的信息,对序列进行扫描线,用线段树在 \(l\) 处给 \(x_{in}\) 时刻的增量加一,在 \(x_{out}+1\) 处给 \(x_{in}\) 时刻的增量减一,对于一个询问 \((x,x_{in})\),相当于在线段树的 \([x_{in},n]\) 这个后缀进行二分,得到最小的 \(x_{out}\) 。用线段树上二分容易实现,通过调整块长可以做到 \(n \sqrt{n \log n}\)

Code:

#include <bits/stdc++.h>
using namespace std;
#define N 100000
int n, m, l, r, x, mx, tag, block, a[100010], c[100010], id[100010], L[100010], R[100010], rig[100010], d[100010];
int f[100010];
inline int read() {
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        s = (s << 3) + (s << 1) + (ch ^ 48);
        ch = getchar();
    }
    return s * w;
}

//用于存储区间修改操作的左、右端点和修改值
struct opera {
    int l, r, x;  // l, r表示区间,x表示修改的值
} b[10*N+5];
//用于存储操作类型
struct Add {
    int t, op;  // t为操作的时间戳,op为操作类型
};
//用于存储区间
struct col {
    int l, r;
};
// 排序用的比较器结构体
struct Cmp {
    int r, id;  // r表示区间右端点,id表示区间的标识
    bool operator < (const Cmp &A) const {
        return r < A.r;  // 按照右端点r进行升序排序
    }
};

//用于存储按右端点排序的区间
multiset <Cmp> s;
multiset <Cmp>::iterator itt;  // multiset的迭代器
vector <int> ask[100010];  // 存储询问操作
vector <Cmp> jia[100010];  // 存储一些区间操作
vector <Add> ad[100010];   // 存储加法操作
vector <col> cun[100010];  // 存储区间数据
map <int, int> mp;
map <int, int>::iterator it;  // map的迭代器
int max(int x, int y) {
    return x > y ? x : y;
}
// modify函数用于区间修改操作,更新某些区间的值
void modify(int l, int r, int ql, int qr, int val) {
    if (l > qr || r < ql) return ;  // 如果当前区间与查询区间没有交集,返回
    if (l <= ql && r >= qr) tag += val;  // 如果当前区间完全包含查询区间,直接更新标记
    else {
        mx = -1e9;  // 初始化最大值
        for (int i = max(l, ql); i <= min(r, qr); i++) {
            c[i] -= val;  // 对指定区间进行更新
        }
        for (int i = ql; i <= qr; i++) {
            mx = max(mx, c[i]);  // 获取区间内的最大值
        }
    }
}

// 处理区间查询和更新
struct segment {
    #define lc(x) x<<1  // 左子树节点索引
    #define rc(x) x<<1|1  // 右子树节点索引

    int lim, flag, now, d[400010];  // lim表示限制条件,flag表示状态标记,now表示当前处理的节点,d为区间的值数组

    // 修改操作,用于更新区间的值
    inline void modify(int k, int l, int r, int x, int y) {
        if (l == r) {
            d[k] += y;  // 如果当前区间是一个单点,直接加上y
            return ;
        }
        int mid = l + r >> 1;  // 计算中间位置
        if (x <= mid) modify(lc(k), l, mid, x, y);  // 如果x在左子树范围内,递归左子树
        else modify(rc(k), mid + 1, r, x, y);  // 如果x在右子树范围内,递归右子树
        d[k] = d[lc(k)] + d[rc(k)];  // 更新当前节点的值
    }

    // 查找操作,用于查找符合条件的节点
    inline void find(int k, int l, int r) {
        if (l == r) {
            now = l;  // 找到目标区间
            return ;
        }
        int mid = l + r >> 1;  // 计算中间位置
        if (lim - d[lc(k)] <= 0) find(lc(k), l, mid);  // 如果左子树满足条件,递归左子树
        else {
            lim -= d[lc(k)];  // 否则减少左子树的值,递归右子树
            find(rc(k), mid + 1, r);
        }
    }

    // 查询操作,用于查询区间内是否满足特定条件
    inline void query(int k, int l, int r, int x, int y) {
        if (x > y) return ;  // 如果查询区间不合法,返回
        if (x <= l && r <= y) {  // 如果当前区间完全在查询区间内
            if (flag) return ;  // 如果已经标记过,返回
            if (lim - d[k] <= 0) {  // 如果当前区间满足条件,更新标记并查找
                flag = 1;
                find(k, l, r);
            }
            lim -= d[k];  // 减去当前区间的值
            return ;
        }
        int mid = l + r >> 1;  // 计算中间位置
        if (x <= mid) query(lc(k), l, mid, x, y);  // 查询左子树
        if (y > mid) query(rc(k), mid + 1, r, x, y);  // 查询右子树
    }
} S;
void build() {
    // 遍历每个元素,将其赋值并进行块编号
	for (int i = 1; i <= n; i++) {
        // 读取数据并存储到数组 a[i]
		a[i] = read();
        // 计算当前元素所属的块编号
		id[i] = (i - 1) / block + 1;
        // 如果当前元素所属的块与前一个元素不同,更新块的左端点 L
		if (id[i] != id[i - 1]) L[id[i]] = i;
        // 更新块的右端点 R
		R[id[i]] = i;
	}
}

// 处理范围修改和询问操作
void addandask() {
    // 遍历每个块
	for (int bl = 1; L[bl]; bl++) {
        mx = tag = 0;
        int ll = L[bl], rr = R[bl];
        
        // 清空队列中右端点小于等于当前块右端点的元素
		while (!s.empty() && (*s.begin()).r <= rr) s.erase(s.begin());

        // 在块内遍历所有元素并更新最大值 mx 和每个元素的值 c[i]
		for (int i = ll; i <= rr; i++) mx = max(mx, a[i]), c[i] = a[i];

        // 遍历 b 数组中的每个操作进行修改和查询
		for (int i = 1, j = 0; i <= m; i++) {
            // 如果最大值大于标记值,则进行修改
			while (j < m + 1 && mx > tag) {
				j ++;
				modify(b[j].l, b[j].r, ll, rr, 1);  // 执行范围修改
			}
            // 记录修改操作后的结果
			f[i] = j;
            // 执行范围修改操作,值减 1
			modify(b[i].l, b[i].r, ll, rr, -1);
		}

        // 处理最后一个操作
		f[m + 1] = m + 1;
        
        // 遍历集合 s 中的元素,更新 rig 数组
		for (itt = s.begin(); itt != s.end(); itt++) {
			rig[(*itt).id] = max(rig[(*itt).id], f[(*itt).id + 1] - 1);
		}

        // 更新当前块内的元素的状态
		for (int i = ll; i <= rr; i++) {
			for (int j = 0; j < jia[i].size(); j++) {
				s.insert(jia[i][j]);
			}
		}
	}

    // 处理 b 数组中的操作
	for (int i = 1; i <= m; i++) {
		l = b[i].l, r = b[i].r, x = b[i].x;
        
        // 如果操作在同一个块内,则直接处理
		if (id[l] == id[r]) {
			for (int j = l; j <= r; j++) {
				ask[j].push_back(i);
			}
		} else {
            // 处理跨块的情况,分别处理左右两部分
			for (int j = l; id[j] == id[l]; j++) {
				ask[j].push_back(i);
			}
			for (int j = r; id[j] == id[r]; j--) {
				ask[j].push_back(i);
			}
		}
        
        // 更新 ad 数组,记录修改操作
		ad[l].push_back((Add) {i, 1});
		ad[r + 1].push_back((Add) {i, -1});
	}
}

// 执行所有查询和修改操作
void san() {
    // 遍历每个元素,处理所有加法操作
	for (int i = 1; i <= n; i++) {
		// 遍历并执行所有加法操作
		for (int j = 0; j < ad[i].size(); j++) {
			int t = ad[i][j].t, op = ad[i][j].op;
			S.modify(1, 1, m, t, op);
		}

		// 遍历所有询问操作并处理
		for (int j = 0; j < ask[i].size(); j++) {
			int fir = ask[i][j];
			S.lim = a[i], S.flag = 0, S.now = m + 1;
			S.query(1, 1, m, fir + 1, m);  // 执行查询
			rig[fir] = max(rig[fir], S.now - 1);  // 更新结果
		}
	}

    // 将结果存储到 cun 数组中
	for (int i = 1; i <= m; i++) {
		cun[b[i].x].push_back((col) {i, rig[i]});
	}

    // 对每个 x 对应的操作进行区间统计
	for (int i = 1; i <= N; i++) {
		if (!cun[i].size()) continue;
		mp.clear();
        
        // 对每个元素进行操作
		for (int j = 0; j < cun[i].size(); j++) {
			mp[cun[i][j].l]++, mp[cun[i][j].r + 1]--;
		}

        // 进行区间修改
		int lst = 0, now = 0;
		mp[m + 1] = 0;
		for (it = mp.begin(); it != mp.end(); it++) {
			if (now) d[lst]++, d[it->first]--;
			now += it->second;
			lst = it->first;
		}
	}
}

// 主函数,程序的入口
int main() {
    // 读取 n 和 m 的值
	n = read(), m = read();
    // 初始化块大小
	block = 190;
    
    // 执行初始化操作
	build();
    
    // 读取 b 数组中的操作
	for (int i = 1; i <= m; i++) {
		b[i].l = read(), b[i].r = read(), b[i].x = read();
		// 记录每个操作对块内元素的影响
		jia[b[i].l].push_back((Cmp) {b[i].r, i});
	}
    
    // 执行添加和询问操作
	addandask();
    
    // 执行结果处理
	san();

    // 输出最终的结果
	for (int i = 1; i <= m; i++) {
		d[i] += d[i - 1];  // 更新累积结果
		printf("%d\n", d[i]);  // 输出每个查询的结果
	}
    
    return 0;  // 程序完美结束
}//这个代码长度刚好6.66KB

纯净版:

#include <bits/stdc++.h>
using namespace std;
#define N 100000
int n, m, l, r, x, mx, tag, block, a[100010], c[100010], id[100010], L[100010], R[100010], rig[100010], d[100010];
int f[100010];
inline int read() {
	int s = 0, w = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		s = (s << 3) + (s << 1) + (ch ^ 48);
		ch = getchar();
	}
	return s * w;
}
struct opera {
	int l, r, x;
} b[10*N+5];
struct Add {
	int t, op;
};
struct col {
	int l, r;
};
struct Cmp {
	int r, id;
	bool operator < (const Cmp &A) const {
		return r < A.r;
	}
};
multiset <Cmp> s;
multiset <Cmp> :: iterator itt;
vector <int> ask[100010];
vector <Cmp> jia[100010];
vector <Add> ad[100010];
vector <col> cun[100010];
map <int, int> mp;
map <int, int> :: iterator it;
inline int max(int x, int y) {
	return x > y ? x : y;
}
void modify(int l, int r, int ql, int qr, int val) {
	if (l > qr || r < ql) return ;
	if (l <= ql && r >= qr) tag += val;
	else {
		mx = -1e9;
		for (int i = max(l, ql); i <= min(r, qr); i++) {
			c[i] -= val;
		}
		for (int i = ql; i <= qr; i++) {
			mx = max(mx, c[i]);
		}
	}
}
struct segment {
#define lc(x) x<<1
#define rc(x) x<<1|1
	int lim, flag, now, d[400010];
	inline void modify(int k, int l, int r, int x, int y) {
		if (l == r) {
			d[k] += y;
			return ;
		}
		int mid = l + r >> 1;
		if (x <= mid) modify(lc(k), l, mid, x, y);
		else modify(rc(k), mid + 1, r, x, y);
		d[k] = d[lc(k)] + d[rc(k)];
	}
	inline void find(int k, int l, int r) {
		if (l == r) {
			now = l;
			return ;
		}
		int mid = l + r >> 1;
		if (lim - d[lc(k)] <= 0) find(lc(k), l, mid);
		else {
			lim -= d[lc(k)];
			find(rc(k), mid + 1, r);
		}
	}
	inline void query(int k, int l, int r, int x, int y) {
		if (x > y) return ;
		if (x <= l && r <= y) {
			if (flag) return ;
			if (lim - d[k] <= 0) {
				flag = 1;
				find(k, l, r);
			}
			lim -= d[k];
			return ;
		}
		int mid = l + r >> 1;
		if (x <= mid) query(lc(k), l, mid, x, y);
		if (y > mid) query(rc(k), mid + 1, r, x, y);
	}
} S;
void build() {
	for (int i = 1; i <= n; i++) {
		a[i] = read();
		id[i] = (i - 1) / block + 1;
		if (id[i] != id[i - 1]) L[id[i]] = i;
		R[id[i]] = i;
	}
}
void addandask() {
	for (int bl = 1; L[bl]; bl++) {
		mx = tag = 0;
		int ll = L[bl], rr = R[bl];
		while (!s.empty() && (*s.begin()).r <= rr) s.erase(s.begin());
		for (int i = ll; i <= rr; i++) mx = max(mx, a[i]), c[i] = a[i];
		for (int i = 1, j = 0; i <= m; i++) {
			while (j < m + 1 && mx > tag) {
				j ++;
				modify(b[j].l, b[j].r, ll, rr, 1);
			}
			f[i] = j;
			modify(b[i].l, b[i].r, ll, rr, -1);
		}
		f[m + 1] = m + 1;
		for (itt = s.begin(); itt != s.end(); itt++) {
			rig[(*itt).id] = max(rig[(*itt).id], f[(*itt).id + 1] - 1);
		}
		for (int i = ll; i <= rr; i++) {
			for (int j = 0; j < jia[i].size(); j++) {
				s.insert(jia[i][j]);
			}
		}
	}
	for (int i = 1; i <= m; i++) {
		l = b[i].l, r = b[i].r, x = b[i].x;
		if (id[l] == id[r]) {
			for (int j = l; j <= r; j++) {
				ask[j].push_back(i);
			}
		} else {
			for (int j = l; id[j] == id[l]; j++) {
				ask[j].push_back(i);
			}
			for (int j = r; id[j] == id[r]; j--) {
				ask[j].push_back(i);
			}
		}
		ad[l].push_back((Add) {
			i, 1
		});
		ad[r + 1].push_back((Add) {
			i, -1
		});
	}
}
void san() {
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < ad[i].size(); j++) {
			int t = ad[i][j].t, op = ad[i][j].op;
			S.modify(1, 1, m, t, op);
		}
		for (int j = 0; j < ask[i].size(); j++) {
			int fir = ask[i][j];
			S.lim = a[i], S.flag = 0, S.now = m + 1;
			S.query(1, 1, m, fir + 1, m);
			rig[fir] = max(rig[fir], S.now - 1);
		}
	}
	for (int i = 1; i <= m; i++) {
		cun[b[i].x].push_back((col) {
			i, rig[i]
		});
	}
	for (int i = 1; i <= N; i++) {
		if (!cun[i].size()) continue;
		mp.clear();
		for (int j = 0; j < cun[i].size(); j++) {
			mp[cun[i][j].l] ++, mp[cun[i][j].r + 1] --;
		}
		int lst = 0, now = 0;
		mp[m + 1] = 0;
		for (it = mp.begin(); it != mp.end(); it++) {
			if (now) d[lst] ++, d[it->first] --;
			now += it->second;
			lst = it->first;
		}
	}
}
int main() {
	n = read(), m = read();
	block = 190;
	build();
	for (int i = 1; i <= m; i++) {
		b[i].l = read(), b[i].r = read(), b[i].x = read();
		jia[b[i].l].push_back((Cmp) {
			b[i].r, i
		});
	}
	addandask();
	san();
	for (int i = 1; i <= m; i++) {
		d[i] += d[i - 1];
		printf ("%d\n", d[i]);
	}
	return 0;
}
posted @ 2025-02-20 18:59  Kaori_Li  阅读(12)  评论(0)    收藏  举报
*/