Codeforces Round 971 (Div. 4)A-G1题解

Codeforces Round 971 (Div. 4)

A

就是b - a

#include <bits/stdc++.h>
#define int long long

using namespace std;

void solve()
{
	int a, b;
	cin >> a >> b;
	cout << b - a << endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int T = 1;
	cin >> T;
	
	while(T -- )
	{
		solve();
	}
	
}

B

简单模拟, 逆序输出即可

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N = 100010;

int stk[N];

void solve()
{
	int n;
	cin >> n;
	
	for(int i = 1; i <= n; i ++ )
	{
		string temp;
		cin >> temp;
		stk[i] = temp.find("#");
	}
	for(int i = n; i >= 1; i -- )
	{
		cout << stk[i] + 1 << " ";
	}
	cout << endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int T = 1;
	cin >> T;
	
	while(T -- )
	{
		solve();
	}
	
}

C

简单模拟

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N = 100010;

int stk[N];

void solve()
{
	int x, y, k;
	cin >> x >> y >> k;
	int ansx = (x + k - 1) / k, ansy = (y + k - 1) / k;
	int ans = 0;
	if(ansx <= ansy)
	{
		ans = ansy * 2;
	}
	else
	{
		ans = ansx * 2 - 1;
	}
	cout << ans << endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int T = 1;
	cin >> T;
	
	while(T -- )
	{
		solve();
	}
	
}

D

看似复杂, 实则简单模拟

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N = 200010;

int n;
int d[N][2];

void init()
{
	for(int i = 0; i <= n; i ++ )
	{
		d[i][0] = 0;
		d[i][1] = 0;
	}
}

void solve()
{
	cin >> n;
	
	init();
	
	for(int i = 1; i <= n; i ++ )
	{
		int x, y;
		cin >> x >> y;
		d[x][y] ++;
		
	}
	
	int ans = 0;
	
	for(int i = 0; i <= n; i ++ )
	{
		if(i != 0 && d[i][0] && d[i - 1][1] && d[i + 1][1])
		{
			ans += min({d[i][0], d[i - 1][1], d[i + 1][1]});
		}
		
		if(i != 0 && d[i][1] && d[i - 1][0] && d[i + 1][0])
		{
			ans += min({d[i][1], d[i - 1][0], d[i + 1][0]});
		}
		
		if(d[i][0] && d[i][1])
		{
			ans += n - d[i][0] - d[i][1];
		}
	}
	cout << ans << endl; 
	
	
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int T = 1;
	cin >> T;
	
	while(T -- )
	{
		solve();
	}
	
}

E

可以分为大于 0 和小于 0 两部分, 答案是离 0 最近的那个数, 二分出最小的正数或负数对应的 i 即可

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N = 200010;

int n, k;

int ss(int k, int i)
{
	return (2 * k + i - 1) * i / 2 - (2 * k + n + i - 1) * (n - i) / 2;
}

bool check(int x)
{
	return ss(k, x) <= 0;
}

int b_search(int l, int r)
{
	while(l < r)
	{
		int mid = l + r + 1 >> 1;
		if(check(mid)) l = mid;
		else r = mid - 1;
	}
	return l;
}

void solve()
{
	cin >> n >> k;

	cout << min(abs(ss(k, temp)), abs(ss(k, temp + 1))) << endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int T = 1;
	cin >> T;
	
	while(T -- )
	{
		solve();
	}
	
}

F

前缀和, 预处理

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 200010;

int n, q, all = 0;

int a[N];
int b[N];

int solve2(int l, int r)
{
	
	int ll = (l + n - 1) / n - 1;
	l %= n;
	if(l == 0) l = n;
	int rr = (r + n - 1) / n - 1;
	r %= n;
	if(r == 0) r = n;
	
	int res = (rr - ll - 1) * b[n];
	
	res += b[ll + n] - b[ll + l - 1];
	
	res += b[rr + r] - b[rr];
	
	return res;
}

void solve()
{
	cin >> n >> q;
	
	for(int i = 1; i <= n; i ++ )
	{
		cin >> a[i];
		b[i] = b[i - 1] + a[i];
	}
	for(int i = n + 1; i <= 2 * n; i ++ )
	{
		b[i] = b[i - 1] + a[i - n];
	}
	
	for(int i = 1; i <= q; i ++ )
	{
		int l, r;
		cin >> l >> r;
		cout << solve2(l, r) << endl;
	}
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int T = 1;
	cin >> T;
	
	while(T -- )
	{
		solve();
	}
	
}

G1

滑动窗口

这里有一个非常好用的技巧, 当我们需要找一个连续的加一上升序列时, 我们在输入时可以输入a[i]后减 i, 然后找相同的数就是连续的加一上升序列

一开始没有想到用滑动窗口, 但是发现超时, 所以用滑动窗口提前处理好, 这样不需要每一次都求一遍

rbegin() 是一个迭代器, 指向最后一个数, rend() 与之相反, 指向第一个数

本题中的 multiset 与 set 的不同在于其中可以有多个相同的数

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 200010;

int n, k, q;

int a[N], b[N];


void solve()
{
	map<int, int> mp;
	multiset<int> S;
	cin >> n >> k >> q;
	
	for(int i = 1; i <= n; i ++ )
	{
		cin >> a[i];
		a[i] -= i;
	}
	
	for(int i = 1; i <= k; i ++ )
	{
		if(mp.find(a[i]) != mp.end()) S.erase(S.find(mp[a[i]]));
		mp[a[i]] ++;
		S.insert(mp[a[i]]);
		b[i] = k -  *S.rbegin();
	}
	
	for(int i = k + 1; i <= n; i ++ )
	{
		S.erase(S.find(mp[a[i - k]]));
		mp[a[i - k]] --;
		S.insert(mp[a[i - k]]);
		
		if(mp.find(a[i]) != mp.end()) S.erase(S.find(mp[a[i]]));
		mp[a[i]] ++;
		S.insert(mp[a[i]]);
		
		b[i] = k -  *S.rbegin();
	}
	
	for(int i = 1; i <= q; i ++ )
	{
		int l, r;
		cin >> l >> r;
		cout << b[r] << endl;
	}
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int T = 1;
	cin >> T;
	
	while(T -- )
	{
		solve();
	}
	
}

G2

 这道题目的内容相对来讲比较复杂, 我会在这里尽可能详细的解释代码及思路, 以便我自己梳理清楚思路

原题链接: G2. Yunli‘s Subarray Queries (hard version)

原题目

题目解析:

这道题给我们一个长度为 n 的数组, q 次询问, 让我们在 l 到 r 的范围内构建一个长度为 k 的连续子数组, 问分别以 l + k - 1 到 r 为连续子数组的结尾, 求需要的操作次数之和

与 easy 版不同的是, 这道题里每个询问都是上道题里一段答案之和, 因此本能的想用一个简单的求和, 然后中间不断取最小值

但是会超时

那么如何去优化这一问题?

我们发现, 这道题要求我们求一段值, 但是这段的值会随着左右区间的不同改变, 将其按左区间从大到小排序, 每次修改一段, 那么在这种情况下, 就能解决左面更小的值让后面的值变小的问题, 而同时修改一段并求一段之和, 这可以用线段树在很短的时间内解决

代码

#include <bits/stdc++.h>

#define int long long

using namespace std;

const int N = 200010;
typedef long long LL; 
int a[N], l[N], r[N], f[N], ans[N], p[N], stk[N];


struct Node
{
	int l, r, tag;
	int sum;
}tr[N * 4];

void build(int u, int l, int r)
{
	tr[u] = {l, r, -1};
	if(l != r)
	{
		int mid = l + r >> 1;
		build(u << 1, l, mid);
		build(u << 1 | 1, mid + 1, r);
	}
}

void pushup(int u, int c)
{
	tr[u].sum = (tr[u].r - tr[u].l + 1) * c;
	tr[u].tag = c;
}

void pushdown(int u)
{
	if(tr[u].tag == -1)
	{
		return;
	}
	
	pushup(u << 1, tr[u].tag);
	pushup(u << 1 | 1, tr[u].tag);
	tr[u].tag = -1;
}

void modify(int u, int l, int r, int c)
{
	if(tr[u].l >= l && tr[u].r <= r)
	{
		pushup(u, c);
	}
	else
	{
		pushdown(u);
		int mid = tr[u].l + tr[u].r >> 1;
		if(l <= mid) modify(u << 1, l, r, c);
		if(r >= mid + 1) modify(u << 1 | 1, l, r, c);
		tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
	}
}

int query(int u, int l, int r)
{
	if(l <= tr[u].l && r >= tr[u].r)
	{
		return tr[u].sum;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	if(r <= mid) return query(u << 1, l, r);
	if(l >=  mid + 1) return query(u << 1 | 1, l, r);
	return query(u << 1, l, r) + query(u << 1 | 1, l, r);
}

void solve()
{
	int n, k, q;
	map<int, int> mp;
	multiset<int> s;
	
	cin >> n >> k >> q;
	for(int i = 1; i <= n; i ++ )
	{
		cin >> a[i];
		a[i] -= i;
		s.insert(0);
	}
	
	for(int i = 1; i <= n; i ++ )
	{
		s.erase(s.find(mp[a[i]]));
		mp[a[i]] ++;
		s.insert(mp[a[i]]);
		
		if(i - k > 0)
		{
			s.erase(s.find(mp[a[i - k]]));
			s.insert(-- mp[a[i - k]]);
		}
		
		if(i >= k)
		{
			f[i - k + 1] = k - *s.rbegin();
		}
	}
	
	for(int i = 1; i <= q; i ++ )
	{
		cin >> l[i] >> r[i];
		p[i] = i;
	}
	
	sort(p + 1, p + q + 1, [&] (int a, int b) {
		return l[a] > l[b];
	});
	
	build(1, 1, n - k + 1);
	int tp = 0;
	stk[ ++ tp] = n - k + 2;
	f[n - k + 2] = -1;
	
	for(int i = 1, j = n - k + 1; i <= q; i ++ )
	{
		while(j >= l[p[i]])
		{
			while(f[stk[tp]] > f[j])
			{
				tp --;
			}
			modify(1, j, stk[tp] - 1, f[j]);
			
			stk[++ tp] = j --;
		}
		ans[p[i]] = query(1, l[p[i]], r[p[i]] - k + 1);
	}
	
	
	for(int i = 1; i <= q; i ++ )
	{
		cout  << ans[i] << "\n";
	}
	
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int T = 1;
	cin >> T;
	while(T -- )
	{
		solve();
	}
}

注释

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 2e5 + 5;

int a[N], f[N], l[N], r[N], p[N];

struct Node 
{
    int l, r, tag;
    LL s;
} tr[N * 4];

int stk[N];
LL ans[N];

//	创建线段树, 模板 
void build(int u, int l, int r) 
{
//	当前这一节点
    tr[u] = {l, r, -1};
//    如果还可以再细分 
    if (l != r) 
	{
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
    }
}

void pushup(int u, int c) 
{
//	区间长度 * 值 
    tr[u].s = (tr[u].r - tr[u].l + 1ll) * c;
    tr[u].tag = c;
}

void pushdown(int u) 
{
//	tag == -1, 代表没改过, 不用管 
    if (tr[u].tag == -1) return;
//    否则把 tag 传下去 
    pushup(u << 1, tr[u].tag);
    pushup(u << 1 | 1, tr[u].tag);
//    当前就没改过了 
    tr[u].tag = -1;
}

//将一个区间里的每个值修改为 c 
void modify(int u, int l, int r, int c) 
{
//	如果在范围内, 就直接算一下当前这段的和为 c * 值个数 
    if (tr[u].l >= l && tr[u].r <= r) 
	{
        pushup(u, c);
    }
    else {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, c);
        if (r >= mid + 1) modify(u << 1 | 1, l, r, c);
        tr[u].s = tr[u << 1].s + tr[u << 1 | 1].s;
    }
}

LL query(int u, int l, int r) 
{
//	如果查询的区间就包裹了当前的node, 那么直接返回即可 
    if (tr[u].l >= l && tr[u].r <= r) 
		return tr[u].s;
	
//	先 pushdown 下一层 
    pushdown(u);
    
    int mid = tr[u].l + tr[u].r >> 1;
//    如果查询的只有左半部分 
    if (r <= mid) return query(u << 1, l, r);
//    如果查询的只有右半部分 
    if (l >= mid + 1) return query(u << 1 | 1, l, r);
    
//    查询的不单独在一侧 
    return query(u << 1, l, r) + query(u << 1 | 1, l, r);
}

void solve() {
    int n, m, k;
    cin >> n >> k >> m;
//    读入a[i] - i 
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        a[i] -= i;
    }
    map<int, int> mp;
    multiset<int> st;
//    插入 n 个 0 以便后续 erase 
    for (int i = 0; i < n; i++) {
        st.insert(0);
    }

    for (int i = 1; i <= n; i++) {
//    	删掉原先当前这个数有几个 
        st.erase(st.find(mp[a[i]]));
//      插入现在这个数有几个 
        st.insert(++mp[a[i]]);
//      如果够 k 个, 就要删掉窗口左面的, 维持窗口的数量为 k 个 
        if (i - k > 0) {
            st.erase(st.find(mp[a[i - k]]));
            st.insert(--mp[a[i - k]]);
        }
//      如果 i >= k, 这时候就知道 k 个数里最少改几个数能行了 
        if (i >= k) f[i - k + 1] = k - *st.rbegin();
    }
    
//  读入要查询的数 
    for (int i = 1; i <= m; i++) {
        cin >> l[i] >> r[i];
//      离散化 
        p[i] = i;
    }
//  将 p 数组按照查询的左区间从大到小去排序 
    sort(p + 1, p + m + 1, [&](int i, int j) {
        return l[i] > l[j];
    });
//  创建线段树 
    build(1, 1, n - k + 1);
//  tp = 0, 作为记录栈顶, 这里手动模拟了一个栈 
    int tp = 0;
//  栈一开始存放 n - k + 2 
    stk[ ++ tp] = n - k + 2;
//    栈对应的位置存一个 -1 
    f[n - k + 2] = -1;
//    一共 m 次询问 
    for (int i = 1, j = n - k + 1; i <= m; i++) 
	{
//		当 j 大于等于 查询的左区间 
        while (j >= l[p[i]]) 
		{
//			拿最小的答案 
            while (f[stk[tp]] > f[j]) 
			{
                tp--;
            }
//            用f[j]修改一片区域的值, 因此需要懒标记
            modify(1, j, stk[tp] - 1, f[j]);
//            栈中存入 j, j -- 
            stk[ ++ tp] = j --;
        }
//        存答案, 由于离散化, 将答案存到了对应的位置上去, query 是线段树的查询, 这里查询的正如题意, 求取总和 
        ans[p[i]] = query(1, l[p[i]], r[p[i]] - k + 1);
    }
    for (int i = 1; i <= m; i++) {
        cout << ans[i] << '\n';
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值