寒假集训第三天---线段树部分题解

CF1268B - Domino for Young

题目大意:给您一排高度从左到右非递增的矩阵,输入为n(1≤n≤300000),代表有n列矩形;接下来一行有n个数字,代表对应矩阵高度(1≤ai≤300000,ai≥ai+1)。问您往里面插12或者21的矩阵最多能插多少个。

解法:贪心考虑每一列,如果是偶数,直接填满1*2,如果是奇数,记录当前列。我们画图可得,如果两个最接近的高度为奇数的列,如果中间有偶数个高度为偶数的矩阵,那都可以消掉(画图很明显)。如果它们之间只有奇数个高度为偶数的矩阵,那怎么也不能消完,最少也会剩下两个点,即两个高度为奇数的矩阵的最下方的两个点(贪心的考虑,留底下比留上面更优)。按照这个思路一直贪心下去就可以了。

🐎

#include <bits/stdc++.h>
using namespace std;
#define ll long long
int main(int argc, char const *argv[])
{
	ll ans = 0 ;
	int n ;
	stack<int> st ;
	while(!st.empty()) st.pop() ;
	scanf("%d",&n) ;
	for(int i = 1, x ; i <= n ; ++ i)
	{
		scanf("%d",&x) ;
		ans += x / 2 ;
		if(x % 2 == 1)
		{
			int tmp = i % 2 ;
			if(!st.size() || st.top() == tmp) st.push(tmp) ;
			else st.pop(), ans ++ ;
		}
	}
	printf("%lld\n",ans) ;
	return 0;
}

POJ 2528 Mayor’s posters
题目大意:按顺序贴海报,后贴的海报会覆盖位于它后方的海报,问最后还能看到多少张海报。
解法:给的长度的可能非常大,有1e9,不加处理直接维护一个线段树肯定会
MLE,TLE,但是我们注意到一共最多只有2e4个点,因此我们可以用离散化的思想先对区间进行预处理
离散化:只考虑元素之间的相互关系,比如 1 10000 10000000000,可以映射成1 2 3

卡点:不是简单的把区间边界离散成点,而是要在去重之后的相邻两个差值大于1的点之间再增加一个点
例如 [1,20] [1,10] [15,20]
离散后区间1覆盖1,4;区间2覆盖1,2;区间3覆盖3,4
但是具体例子中并没有完全被覆盖,用上述方法就可以解决这个问题。

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
using namespace std;
const int maxn = 1e4 + 10 ;
int n ;
int vis[maxn<<3],sum[maxn<<4] ;
int li[maxn<<1],ri[maxn<<1],lsh[maxn<<2] ;
void pushdown(int rt)
{
	sum[rt<<1] = sum[rt] ;
	sum[rt<<1|1] = sum[rt] ;
	sum[rt] = -1 ;
}
void update(int L, int R, int C, int l, int r, int rt)
{
	if(L <= l && r <= R)
	{
		sum[rt] = C ;
		return ;
	}
	if(sum[rt] != -1) pushdown(rt) ;
	int mid = (r + l) / 2 ;
	if(mid >= R) update(L,R,C,l,mid,rt<<1) ;
	else if(L > mid) update(L,R,C,mid+1,r,rt<<1|1) ;
	else update(L,mid,C,l,mid,rt<<1),update(mid+1,R,C,mid+1,r,rt<<1|1) ;
}
int ans ;
void query(int l, int r, int rt)
{
	if(!vis[sum[rt]] && sum[rt] != -1)
	{
		ans ++ ;
		vis[sum[rt]] = 1 ;
		return ;
	}
	if(l == r) return ;
	if(sum[rt] != -1) pushdown(rt) ;
	int mid = (l + r) / 2 ;
	query(l,mid,rt<<1) ;
	query(mid+1,r,rt<<1|1) ;
}
int main(int argc, char const *argv[])
{
	int t ;
	scanf("%d",&t) ;
	while(t --)
	{
		scanf("%d",&n) ;
		memset(sum,-1,sizeof sum) ;
		memset(vis,0,sizeof vis) ;
		int tot = 0 ;
		for(int i = 0 ; i < n ; ++ i)
		{
			scanf("%d %d",&li[i],&ri[i]) ;
			lsh[tot ++] = li[i] ;
			lsh[tot ++] = ri[i] ;
		}
		sort(lsh,lsh+tot) ;
		int tot = unique(lsh,lsh+tot) - lsh ;
		int tmp = tot ;
		for(int i = 1 ; i < tmp ; ++ i)
		{
			if(lsh[i] - lsh[i-1] > 1) lsh[tot ++] = lsh[i - 1] + 1 ;
		}
		sort(lsh,lsh+tot) ;
		for(int i = 0 ; i < n ; ++ i)
		{
			int x = lower_bound(lsh,lsh+tot,li[i]) - lsh ;
			int y = lower_bound(lsh,lsh+tot,ri[i]) - lsh ;
			update(x,y,i,0,tot-1,1) ;
		}
		ans = 0 ;
		query(0,tot-1,1) ;
		printf("%d\n",ans) ;
	}
	return 0;
}

HDU 3016 Man Down
题目大意:二维平面内有n条线段(2<=n<=1e5),线段端点为(Li,Hi),(Ri,Hi).
(0<Li<Ri<1e5),(Hi>0).你一开始位于最高的线段上,有100HP,往左或往右垂直下落,你每跳到一个线段上都会获得线段相应的HP(-1000 <= HP<= 1000),问你跳到地面时最大HP,中途HP<=0使游戏结束。
思路:如果不看数据范围可以直接用DP或者最长路做。但考虑到n是1e5,所以我们需要优化我们的DP或者最长路。对于DP,我们在对每个状态转移的过程中,我后一个状态是从之前n个状态转移过来,但是这题目中是垂直下落,所以一个下落点最多对应一条线段。所以我们只需要找到与当前状态有关的前状态。对于最长路,对应的是我一个点最多对应两条与之相连的边,如果可以快速找到有关线段那复杂度就可以大大降低。
思路:如果不看数据范围可以直接用DP或者最长路做。但考虑到n是1e5,所以我们需要优化我们的DP或者最长路。对于DP,我们在对每个状态转移的过程中,我后一个状态是从之前n个状态转移过来,但是这题目中是垂直下落,所以一个下落点最多对应一条线段。所以我们只需要找到与当前状态有关的前状态。对于最长路,对应的是我一个点最多对应两条与之相连的边,如果可以快速找到有关线段那复杂度就可以大大降低。
难点:我们借助工具树 线段树即可。对于高度从低到高排序,因为高度较高的线段会覆盖高度较低的线段。每次加入线段前对于左右端点单点查询,查询过后,做相应记录,然后对于这条线段覆盖的范围进行区间更新即可。之后跑最长路或者DP都可
代码参考博客

#include <iostream>
#include <cstdio>
#include <queue>
#include <stack>
#include <algorithm>
#include <cstring>
using namespace std ;
inline void read (int &x)
{
    char ch = getchar() ; int f = 0 ; x = 0 ;
    while (!isdigit(ch)) {if (ch == '-') f = 1 ; ch = getchar() ;}
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar() ;
    if (f) x = -x ;
}
const int N = 1E5 + 10 ;
int n, cnt, M, c[N << 2], d[N], to[N][2], vis[N] ;
struct e
{
    int h, l, r, val ;
    bool operator < (const e &x) const {return h < x.h ;}
} a[N] ;
#define ls p << 1
#define rs p << 1 | 1
inline void push_down (int p)
{
    if (c[p]) c[ls] = c[rs] = c[p], c[p] = 0 ;
}
int update (int p, int l, int r, int ql, int qr, int val)
{
    if (ql <= l && qr >= r)
    {
    	c[p] = val ;
    	return 0 ;
    }
    push_down (p) ;
    int mid (l + r >> 1) ;
    if (ql <= mid) update (ls, l, mid, ql, qr, val) ;
    if (qr > mid) update (rs, mid + 1, r, ql, qr, val) ;
}
int query (int p, int l, int r, int pos)
{
    if (l == r) return c[p] ;
    push_down (p) ;
    int mid (l + r >> 1) ;
    return pos <= mid ? query (ls, l, mid, pos) : query (rs, mid + 1, r, pos) ;
}
inline void Spfa ()
{
    queue <int> q ;
    memset (d, 0xcf, sizeof (d)) ;
    memset (vis, 0, sizeof (vis)) ;
    d[n] = 100 + a[n].val, vis[n] = 1, q.push (n) ;
    while (!q.empty())
    {
        int u = q.front () ;
        vis[u] = 0, q.pop () ;
        for (int i = 0 ; i <= 1 ; ++ i)
        {
            int v = to[u][i] ;
            if (d[u] + a[v].val > d[v] && d[u] + a[v].val > 0)
            {
                d[v] = d[u] + a[v].val ;
                if (!vis[v]) q.push (v), vis[v] = 1 ;
            }
        }
    }
}
int main()
{
    while (~scanf ("%d", &n))
    {
        cnt = M = 0 ;
        memset (c, 0, sizeof (c)) ;
        for (int i = 1 ; i <= n ; ++ i)
            read (a[i].h), read (a[i].l), read (a[i].r), read (a[i].val), M = max (M, a[i].r) ;
        sort (a + 1, a + n + 1) ;
        for (int i = 1 ; i <= n ; ++ i)
        {
            int tl = query (1, 1, M, a[i].l), tr = query (1, 1, M, a[i].r) ;
            to[i][0] = tl, to[i][1] = tr ;
            update (1, 1, M, a[i].l, a[i].r, i) ;
        }
        Spfa () ;
        if (d[0] < 0) puts ("-1") ;
        else printf ("%d\n", d[0]) ;
    }
    return 0 ;
}

CodeForces - 1283C
题目大意:题目给一个f数组,f[i]表示第i个人会送礼物给第f[i]个人,f[i]为0表示第i个人还不知道要给谁送礼物。要求构造一个f数组即填满f数组中的0,使得每个人都只收到一次礼物并且只送出去一次礼物。

题解:先设置一个out和in数组,out[i]=1表示第i个人送出去了一次礼物,in[i]=1表示这个人收入一次礼物。先处理out和in数组都等于0的点,若这样的点数量为1个,则任意找个in为1并且out为0的点并接上这个点;若这样的点的数量大于1,则让这几个点自己成一个环。最后再把所有in=1并且out=0的点指向out=1并且in=0的点即可。

代码

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
#define ll long long
const int maxn = 200000 + 10;
const int maxm = 10 + 10;
inline ll read() {
	char ch = getchar(); ll x = 0, f = 1;
	while (ch<'0' || ch>'9') { if (ch == '-')f = -1; ch = getchar(); }
	while ('0' <= ch && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
	return x * f;
}
int n;
int f[maxn], in[maxn], out[maxn];
int b[maxn];  //表示in=0并且out=0的点
int main() {
	n = read();
	for (int i = 1; i <= n; ++i) {
		f[i] = read();
		if (f[i] != 0) {
			out[i] = 1;
			in[f[i]] = 1;
		}
	}
	int num = 0;
	for (int i = 1; i <= n; ++i) {
		if (in[i] == 0 && out[i] == 0) {
			b[++num] = i;
		}
	}
	if (num == 1) {
		for (int i = 1; i <= n; ++i) {
			if (in[i] == 1 && out[i] == 0) {
				f[i] = b[1];
				out[i] = 1;
				in[f[i]] = 1;
				break;
			}
		}
	}
	else if (num > 1) {      //自成环
		b[++num] = b[1];
		for (int i = 1; i < num; ++i)
		{
			f[b[i]] = b[i + 1];
			out[b[i]] = 1;
			in[f[b[i]]] = 1;
		}
	}
	vector<int>va, vb; //va推入out=1&&in=0的点,vb反之
	for (int i = 1; i <= n; ++i) {
		if (out[i] == 1 && in[i] == 0)va.push_back(i);
		if (in[i] == 1 && out[i] == 0)vb.push_back(i);
	}
	int sz = va.size();
	for (int i = 0; i < sz; ++i) {
		f[vb[i]] = va[i];
		out[vb[i]] = 1;
		in[f[vb[i]]] = 1;
	}
	for (int i = 1; i <= n; ++i) {
		if (i != 1)printf(" ");
		printf("%d", f[i]);
	}
	puts("");
}

HDU - 1540
题意:给你n个数起始每个数为1,m次询问。
每次询问有三种操作
D x 使第x个数字变为0
Q x 询问x所在的最长子序列和为多少(必须包含x,并且该子序列必须全为1)
R 恢复上一次“D x”的x,即使x变为1

题解:线段树前缀后缀的处理

代码

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
#define ll long long
const ll INF = 1e18;
const int maxn = 50000 + 10;
inline ll read() {
	char ch = getchar(); ll x = 0, f = 1;
	while (ch<'0' || ch>'9') { if (ch == '-')f = -1; ch = getchar(); }
	while ('0' <= ch && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
	return x * f;
}
int n, m;
struct node {
	int l, r;
	int pre, suf, maxx;
}t[maxn << 2];
int a[maxn];
void pushup(int rt) {                 //向上更新
	t[rt].pre = t[rt << 1].pre;
	t[rt].suf = t[rt << 1 | 1].suf;
	if (t[rt].pre == t[rt << 1].r - t[rt << 1].l + 1)t[rt].pre += t[rt << 1 | 1].pre;
	if (t[rt].suf == t[rt << 1 | 1].r - t[rt << 1 | 1].l + 1)t[rt].suf += t[rt << 1].suf;
	t[rt].maxx = max(t[rt << 1].suf + t[rt << 1 | 1].pre, max(t[rt << 1].maxx, t[rt << 1 | 1].maxx));
}
void build(int rt, int l, int r) {
	t[rt].l = l, t[rt].r = r;
	t[rt].pre = t[rt].suf = t[rt].maxx = 0;
	if (l == r) {
		t[rt].pre = t[rt].suf = t[rt].maxx = 1;
		return;
	}
	int mid = (l + r) >> 1;
	build(rt << 1, l, mid);
	build(rt << 1 | 1, mid + 1, r);
	pushup(rt);
}
void update(int rt, int l, int r, int pos, int d) {
	if (l == pos && r == pos) {
		t[rt].pre = t[rt].suf = t[rt].maxx = d;
		return;
	}
	int mid = (t[rt].l + t[rt].r) >> 1;
	if (pos <= mid)
		update(rt << 1, l, mid, pos, d);
	else
		update(rt << 1 | 1, mid + 1, r, pos, d);
	pushup(rt);
}
int query(int rt, int l, int r, int pos) {
	if (t[rt].maxx == 0 || l == r || t[rt].maxx == (r - l + 1))return t[rt].maxx;
	int mid = (l + r) >> 1;
	if (pos <= mid){
		if (t[rt << 1].r - pos + 1 <= t[rt << 1].suf)return t[rt << 1].suf + t[rt << 1 | 1].pre;
		else return query(rt << 1, l, mid, pos);
	}
	else {
		if (pos - t[rt << 1 | 1].l + 1 <= t[rt << 1 | 1].pre)return t[rt << 1 | 1].pre + t[rt << 1].suf;
		else return query(rt << 1 | 1, mid + 1, r, pos);
	}
}
int main() {
	while (scanf("%d %d", &n, &m) != EOF) {
		build(1, 1, n);
		int len = 0;
		while (m--){
			char op[2];
			int x;
			scanf("%s", op);
			if (op[0] == 'D') {
				scanf("%d", &x);
				//puts("1");
				update(1, 1, n, x, 0);
				a[++len] = x;
			}
			else if (op[0] == 'Q'){
				scanf("%d", &x);
				printf("%d\n", query(1, 1, n, x));
			}
			else{
				update(1, 1, n, a[len], 1);
				len--;
			}
		}
	}
}

CodeForces - 1262D2

题意:给定一个长度为n的数组,求一个长度为k的最佳序列,该序列满足两个条件:其和是所有长度相同的序列的和的最大值;该序列是所有长度相同且和最大的序列中,其数字构成的序列的字典序最小,输出该序列的第pos位的数字,多次询问。

题解:首先按数组中的下标建一棵线段树,假设原数组是a,我们用一个新数组b记录a,将b数组先按权值排序、再按下标排序,然后再用数组记录m次询问,按k从小到大排序,再对每个询问二分线段树右边界,最后把m次询问按原来的顺序排回来,最后按顺序输出答案即可。

代码

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
#define ll long long
const int maxn = 200000 + 10;
inline ll read() {
	char ch = getchar(); ll x = 0, f = 1;
	while (ch<'0' || ch>'9') { if (ch == '-')f = -1; ch = getchar(); }
	while ('0' <= ch && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
	return x * f;
}

struct node {
	int v, id;
}a[maxn], b[maxn];
struct q {
	int k, pos, id, ans;
}q[maxn];
int n, m;
bool cmp1(struct node &a, struct node &b) {
	if (a.v != b.v)return a.v > b.v;
	return a.id < b.id;
}
bool cmp2(struct q &a, struct q &b) {
	return a.k < b.k;
}
bool cmp3(struct q &a, struct q &b) {
	return a.id < b.id;
}
struct tree {
	int l, r;
	int sum;
}t[maxn << 2];
void pushup(int rt) {
	t[rt].sum = t[rt << 1].sum + t[rt << 1 | 1].sum;
}
void build(int rt, int l, int r) {
	t[rt].l = l, t[rt].r = r;
	t[rt].sum = 0;
	if (l == r) {
		return;
	}
	int mid = (l + r) >> 1;
	build(rt << 1, l, mid);
	build(rt << 1 | 1, mid + 1, r);
	pushup(rt);
}
void update(int rt, int l, int r, int pos) {
	if (l == pos && r == pos) {
		t[rt].sum = 1;
		return;
	}
	int mid = (l + r) >> 1;
	if (pos <= mid)update(rt << 1, l, mid, pos);
	else update(rt << 1 | 1, mid + 1, r, pos);
	pushup(rt);
}
int query(int rt, int ql, int qr) {
	if (t[rt].l == ql && t[rt].r == qr) {
		return t[rt].sum;
	}
	int mid = (t[rt].l + t[rt].r) >> 1;
	if (mid >= qr)return query(rt << 1, ql, qr);
	else if (ql > mid)return query(rt << 1 | 1, ql, qr);
	else return(query(rt << 1, ql, mid) + query(rt << 1 | 1, mid + 1, qr));
}
int main() {
	n = read();
	for (int i = 1; i <= n; ++i) {
		a[i].v = read();
		a[i].id = i;
		b[i] = a[i];
	}
	sort(b + 1, b + 1 + n, cmp1);
	m = read();
	for (int i = 1; i <= m; ++i) {
		q[i].k = read();
		q[i].pos = read();
		q[i].id = i;
		q[i].ans = 0;
	}
	sort(q + 1, q + 1 + m, cmp2);
	build(1, 1, n);
	int now = 1;
	for (int i = 1; i <= m; ++i) {
		while (now <= q[i].k) { update(1, 1, n, b[now].id); now++; }
		int l = 1, r = n, ans = n;
		while (l <= r) {
			int mid = (l + r) >> 1;
			if (query(1, 1, mid) < q[i].pos) {
				l = mid + 1;
			}
			else ans = mid, r = mid - 1;
		}
		q[i].ans = a[ans].v;
	}
	sort(q + 1, q + 1 + m, cmp3);
	for (int i = 1; i <= m; ++i) {
		printf("%d\n", q[i].ans);
	}
}
发布了1 篇原创文章 · 获赞 1 · 访问量 73
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 书香水墨 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览