Loading

IOI 2026 中国国家集训队作业(试题泛做)个人题解

QOJ 4802 Ternary Search

QOJ 4802 Ternary Search

题意

给你一个长度为 \(n\) 的序列 \(a\),对每一个前缀查询:

  • 任意交换相邻两项,把序列变成单峰或单谷的最小操作次数。

\(n \le 2 \times 10 ^ 5, a_i \le 10 ^ 9\)

思路

单峰和单谷可以互相转换,方法是 \(\forall i \in [1, n], a_i \leftarrow n + 1 - a_i\)
把单峰转换为单谷。

Part 1

看到求解邻项交换次数最小值的问题,应该想到转换为求解逆序对个数最小值。
但是很不好,这是单谷序列而不是单调序列。

对于一个单谷的序列:

image

我们想要把前半部分转换一下,使得整个序列变成单调的,不难想到对一个前缀取相反数。

所以我们现在这样描述单谷序列:

  • 可以将一个前缀取相反数后得到递增序列。

image

Part 2

我们现在的问题是在 \(a\) 的一个前缀里选择一些数取相反数,求逆序对个数的最小值。

因为逆序对研究数的相对大小,那么我们考虑最大值 \(mx\) 这个特殊的元素。
可以发现,最大值在取反后是严格的最小值,这意味着他只能和一侧的数构成逆序对,并且与剩下数是否取反无关,也就可以方便地计算逆序对。

具体地,对于最大值:

  • 如果不取反,逆序对个数是在 \(mx\) 右边的数个数
  • 如果取反,逆序对个数是在 \(-mx\) 左边的数的个数。
    然后我们可以把 \(mx\) 删掉不考虑,然后得到一个有新的 \(mx'\) 的子问题。

推广上述结论,对于一个数 \(x\)

  • 如果不取反,逆序对个数就是「在 \(x\) 左边的数中,比 \(x\) 晚被删去」的数的个数。
    也就是「\(x\) 左边小于 \(x\) 的数个数」。
  • 如果取反,就是「\(x\) 右边小于 \(x\) 的数个数」。

Part 3

接下来我们考虑新加入一个数的时候,答案的改变情况。

\(wl_i\) 为「\(a_i\) 左边小于 \(a_i\) 的数个数」,\(wr_i\) 为「\(a_i\) 右边小于 \(a_i\) 的数个数」。

可以发现 \(wl_i\)\(a_i\) 加入的时候就已经确定了。
而随着更多的数加入,\(wr_i\) 逐渐增加,直到某一时刻超过 \(wl_i\)
所以 \(\min(wl_i, wr_i)\) 会在某一时刻前取 \(wr_i\),该时刻后取 \(wl_i\)

而我们需要支持加入 \(a_i\) 时给 \(\forall j, a_j \gt a_i\)\(wr_j\) 更新,且需要支持单点修改。
考虑权值线段树维护。

对于这种「每个元素都有一个不同的阈值」类型问题(瞎起的名字),可以维护 \(d_i = wl_i - wr_i\),表示距离阈值的差距。(在所有时刻,保证 \(d_i \ge 0\)。)
每一次的更改就变成了给权值线段树上 \((a_i, n]\) 的区间 \(d\) 自减(\(d = 0\) 除外)。
每一个前缀的答案就是 \(\sum \min(wl_i, wr_i) = \left( \sum_i wl_i \right) - \left( \sum_i d_i \right)\)


线段树需要维护 \(\sum d\),而且我们每次区间修改 \(d \leftarrow d - 1\) 时需要忽略 \(d = 0\) 的,
所以需要维护 \(cnt\) 表示区间内 \(d \not = 0\) 的个数,同时需要在区间修改后清空 \(d = 0\) 对应的 \(cnt\)
此处暴力清空即可,每个下标只会被清空一次,清空总时间复杂度 \(\mathcal{O}(n \log n)\)

另外在给 \(d\) 赋初始值 \(wl\) 时需要树状数组维护一下。

总时间复杂度 \(\mathcal{O}(n \log n)\)

代码

record

const int N = 2e5 + 5;
const ll inf = 0x3f3f3f3f3f3f3f3f;

int n;
int a[N];
int v[N], vn;

ll ans[N];

struct BIT{
	int c[N];
	void init(){
		rep(i, 1, n) c[i] = 0;
	}
	int lowbit(int x){
		return x & -x;
	}
	void add(int i, int v){
		for(; i <= n; i += lowbit(i)){
			c[i] += v;
		}
	}
	int query(int i){
		int res = 0;
		for(; i; i -= lowbit(i)){
			res += c[i];
		}
		return res;
	}
} BIT;

struct SMT{
	struct node{
		ll sum;
		int mn, tag, cnt;
		// sum_d, mn_d, add_tag, count_of_d_=_0
	} t[N << 2];
	
	#define ls(x) (x << 1)
	#define rs(x) ((x << 1) | 1)
	#define mid ((l + r) >> 1)
	
	void push_up(int x){
		t[x].sum = t[ls(x)].sum + t[rs(x)].sum;
		t[x].cnt = t[ls(x)].cnt + t[rs(x)].cnt;
		t[x].mn = inf;
		if(t[ls(x)].cnt) t[x].mn = min(t[x].mn, t[ls(x)].mn);
		if(t[rs(x)].cnt) t[x].mn = min(t[x].mn, t[rs(x)].mn);
	}
	void hard(int x, int v){
		t[x].tag += v;
		t[x].sum += t[x].cnt * v;
		if(t[x].cnt) t[x].mn += v;
	}
	void push_down(int x){
		if(t[x].tag){
			hard(ls(x), t[x].tag);
			hard(rs(x), t[x].tag);
			t[x].tag = 0;
		}
	}
	void build(int x, int l, int r){
		if(l == r){
			t[x].mn = inf;
			t[x].sum = t[x].cnt = 0;
			t[x].tag = 0;
			return;
		}
		build(ls(x), l, mid);
		build(rs(x), mid + 1, r);
		push_up(x);
	}
	void modify_point(int x, int l, int r, int p, int v){
		if(l == r){
			t[x].sum = t[x].mn = v;
			t[x].cnt = (v != 0);
			return;
		}
		push_down(x);
		if(p <= mid) modify_point(ls(x), l, mid, p, v);
		else modify_point(rs(x), mid + 1, r, p, v);
		push_up(x);
	}
	void add_range(int x, int l, int r, int ql, int qr, int v){
		if(ql > qr) return;
		if(t[x].cnt == 0) return;
		if(ql <= l && r <= qr){
			hard(x, v);
			return;
		}
		push_down(x);
		if(ql <= mid) add_range(ls(x), l, mid, ql, qr, v);
		if(qr > mid) add_range(rs(x), mid + 1, r, ql, qr, v);
		push_up(x);
	}
	void update(int x, int l, int r, int ql, int qr){
		if(ql > qr) return;
		if(t[x].mn) return;
		if(l == r){
			t[x].cnt = 0;
			return;
		}
		push_down(x);
		if(ql <= mid) update(ls(x), l, mid, ql, qr);
		if(qr > mid) update(rs(x), mid + 1, r, ql, qr);
		push_up(x);
	}
	
	#undef ls
	#undef rs
	#undef mid
} SMT;

void solve(){
	BIT.init();
	SMT.build(1, 1, n);
	
	ll sum_l = 0;
	rep(i, 1, n){
		int cnt_l = BIT.query(a[i]);
		sum_l += cnt_l;
		BIT.add(a[i], 1);
		SMT.modify_point(1, 1, n, a[i], cnt_l);
		SMT.add_range(1, 1, n, a[i] + 1, n, -1);
		SMT.update(1, 1, n, a[i] + 1, n);
		ans[i] = min(ans[i], sum_l - SMT.t[1].sum);
	}
}

void solve_test_case(){
	n = read();
	rep(i, 1, n){
		a[i] = read();
		v[i] = a[i];
		ans[i] = inf;
	}
	
	sort(v + 1, v + n + 1);
	vn = unique(v + 1, v + n + 1) - v - 1;
	
	rep(i, 1, n){
		a[i] = lower_bound(v + 1, v + vn + 1, a[i]) - v;
	}
	solve();
	
	rep(i, 1, n) a[i] = vn + 1 - a[i];
	solve();
	
	rep(i, 1, n){
		write(ans[i]);
	}
}

QOJ 4788 Gravity

题意

给你一个 \(n \times n\) 的网格图,每个格子有障碍('#')或者没有('.'),一个极大的障碍的四联通块算一个物品,所有物品按照重力往下掉,要求输出最后的结果。

\(n \le 2000\)

思路

先 bfs 把所有的连通块搜出来。

..........
..######..
..#....#..
..#.#..#..
..#..#.#..
..#....#..
..######..
..........
..#....#..
.......#..

然后按照竖列考虑,对于一个竖列上下相邻的两个物品,上面的物品最多只能下落到下面物品的位置。

\(h_i\) 表示第 \(i\) 个物品的下落高度,\(id_{(x, y)}\)\((x, y)\) 这个障碍所在的物品编号。(\(x\) 是行编号,\(y\) 是列编号)

对于 \((x_1, y), (x_2, y), x_1 \lt x_2, \not \exists x_1 \lt x_3, \lt x_2, (x_3, y) 是障碍\) 则称 \((x_1, y), (x_2, y)\) 同列相邻。
则对于所有同列相邻的 \((x_1, y), (x_2, y), h_{id_{(x_1, y)}} \le h_{id_{(x_2, y)}} + (x_2 - x_1 - 1)\)

差分约束系统,将 \(id_{(x_1, y)}\)\(id_{(x_2, y)}\) 连边。这里方便考虑,把整个 \(n + 1\) 行当作一个物品。

代码

record

char read_ch(){
	char c = getchar();
	for(; c != '#' && c != '.'; ) c = getchar();
	return c;
}

const int N = 2005;

int n, m;
bool mp[N][N], mp2[N][N];

int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
int bid[N][N], bcnt;
void bfs(int sx, int sy, int cur_id){
	queue<pair<int, int>> q;
	q.push({sx, sy});
	bid[sx][sy] = cur_id;
	while(!q.empty()){
		int x = q.front().first, y = q.front().second;
		q.pop();
		rep(d, 0, 3){
			int nx = x + dx[d];
			int ny = y + dy[d];
			if(!bid[nx][ny] && 1 <= nx && nx <= n && 1 <= ny && ny <= m && mp[nx][ny]){
				bid[nx][ny] = cur_id;
				q.push({nx, ny});
			}
		}
	}
}

int calc(int x, int y){
	return (x - 1) * n + y;
}
pair<int, int> rev_calc(int id){
	int y = id % n, x = id / n + 1;
	return make_pair(x, y);
}

struct edge{
	int v, w;
};
vector<edge> e[N * N];

int dis[N * N];
bool vis[N * N];
void dijkstra(){
	memset(dis, 0x3f, sizeof(dis));
	dis[bcnt] = 0;
	priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
	q.push({dis[bcnt], bcnt});
	while(!q.empty()){
		int u = q.top().second; q.pop();
		if(vis[u]) continue;
		vis[u] = 1;
		for(edge E : e[u]){
			int v = E.v, w = E.w;
			if(vis[v]) continue;
			if(dis[v] > dis[u] + w){
				dis[v] = dis[u] + w;
				q.push({dis[v], v});
			}
		}
	}
}
signed main(){
	n = read(), m = read();
	rep(i, 1, n){
		rep(j, 1, m){
			mp[i][j] = (read_ch() == '#');
		}
	}
	rep(i, 1, n){
		rep(j, 1, m){
			if(mp[i][j] && !bid[i][j]){
				bfs(i, j, ++bcnt);
			}
		}
	}
	
	bcnt++;
	rep(j, 1, m){
		bid[n + 1][j] = bcnt;
	}
	rep(x, 1, n + 1){
		rep(y, 1, m){
			int up = x - 1;
			while(up && !mp[up][y]) up--;
			if(bid[up][y] == bid[x][y] || mp[up][y] == 0) continue;
			e[bid[x][y]].push_back({bid[up][y], x - up - 1});
		}
	}
	dijkstra();
	rep(i, 1, n){
		rep(j, 1, m){
			if(mp[i][j]){
				mp2[i + dis[bid[i][j]]][j] = 1;
			}
		}
	}
	rep(i, 1, n){
		rep(j, 1, m){
			if(mp2[i][j]){
				putchar('#');
			}else{
				putchar('.');
			}
		}
		puts("");
	}
    return 0;
}
posted @ 2025-12-18 22:46  lajishift  阅读(37)  评论(0)    收藏  举报