题目大意:给您一排高度从左到右非递增的矩阵,输入为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--;
}
}
}
}
题意:给定一个长度为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);
}
}
来源:CSDN
作者:wifePI
链接:https://blog.csdn.net/weixin_43901912/article/details/103982125