ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

AtCoder 题解集合

2022-06-24 02:31:21  阅读:147  来源: 互联网

标签:AtCoder typedef int 题解 ll long leq 集合 define


目录

AGC

ARC

ABC

ABC 150

train#1

D. Semi Common Multiple (LCM, 数学推导)

题意

给一个长度为 \(n(1\leq n \leq 10^5)\) 数组且 \(a_i\) 是偶数, 求 \([1,M]\) 有多少个 \(X\) 满足对任意 \(X = a_i *(p + 0.5)\)

数据范围
\(1\leq M\leq 10^9\)

思路

  • X 肯定是 \(a_i\) 的公倍数, 转换一下式子
  • \(X = \frac{a_i}{2} * (2*p + 1)\), 即 X 一定是 \(\frac{a_i}{2}\) 的奇数倍, 那么只需要 check 一下就好了, 当然 \(X \leq M\)

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define mkp make_pair
#define endl "\n"
using namespace std;
const ll INF = 2e18;

ll gcd(ll a, ll b){
	return b ? gcd(b, a % b) : a;
}

int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	ll n, m;
	cin >> n >> m;
	vector<ll> a(n);
	ll LCM = 1;
	bool fl = false;
	for(int i = 0;i < n; i++){
		cin >> a[i];
		a[i] /= 2;
		if(LCM == INF)
			continue;
		LCM = LCM / gcd(a[i], LCM) * a[i];
		if(LCM > m)
			LCM = INF;
	}
	if(LCM == INF){
		cout << 0 << endl;
		return 0;
	}
	for(int i = 0; i < n; i++){
		if((LCM / a[i]) % 2 == 0)
			fl = true;
	}
	if(LCM == INF || fl)
		cout << 0 << endl;
	else
		cout << 1ll * (m / LCM + 1) / 2 << endl;
    return 0;
}

E. Change a Little Bit (组合计数推导)

题意

给定长度为 \(n\) 的两个01串 \(S,T\), 定义 \(f(S,T)\) 为以下操作最小花费总和:

改变 \(S[i]\) , 此次花费定义为操作之前 \(S\) 和 \(T\) 不同下标数 \(D*C_i\)

数据范围
\(1\leq N\leq 2 * 10^5\)
\(1\leq C_i\leq 10^9\)

思路

  • 考虑按贡献来计算, 按照每一个 \(C_i\) 的贡献来计算, 显然贪心地想, 大的 \(C_i\) 要后选, 小的先选
  • 那么假设 \(C_i\) 已从大到小排序, \(1\leq i\leq n\), 当 \(i\) 不同时, 那么设不包括 \(i\) 下标有 \(j\) 个数不同, 则 \(j\leq i\) , 否则贪心不成立
  • 那么枚举一下 \(j\) 的大小, \(j=0\) 时, 前面全是相同的数, 其余的数(除开 \(i\)) 总共有 \(n-i\) 个随便什么情况(因为按贡献考虑互相独立), 所以此次贡献为 \(2^{n-i}*C[i]\)
  • 更一般的来看, 对于每个 \(j\), 贡献为 \(2^{n-i}*C_i^j * (j + 1) * C[i]\), 则对于每个 \(C[i]\) 其贡献为 \(2^{n-i}*C[i] * \Sigma_{j=0}^{i-1} C_{i-1}^j * (j+1)\)
  • 再转化一下, 利用公式 \(C_n^i*i = n*2^{n-1}\), \(原式=2^{n-i}*C[i]*((i-1)*2^{i-2} + 2^{i-1}\) , 代码中 \(i\) 从 0 开始.
  • 最后一定不要漏掉, 关于每个位置的相同与不同定义是可以变化的 \(01,10\), \(00,11\) 分别属于不同与相同位与位之间可以任意组合 , 所以最后答案还要乘上 \(2^n\)

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define mkp make_pair
#define endl "\n"
using namespace std;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
ll mi[N];

// n * 2 ^n-1
int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int n;
	cin >> n;
	vector<ll> c(n);
	mi[0] = 1;
	for(int i = 1; i <= n; i++)
		mi[i] = mi[i - 1] * 2ll % mod;
	for(int i = 0; i < n; i++)
		cin >> c[i];
	sort(arr(c));
	reverse(arr(c));
	ll ans = 0;
	for(int i = 0; i < n; i++){
		int x = i + 1;
		ans = (ans + mi[n - x] % mod * c[i] % mod * (i * mi[max(i - 1, 0)] % mod + mi[i])) % mod;
	}
	cout << mi[n] * ans % mod << endl;
    return 0;
}

F. Xor Shift (XOR差分性 + KMP)

题意

给了长度为 \(n\) 的两个序列 \(a,b\), 找出所有的 \((k,x)\) 满足 \(b_i=a_{i+k\mod N} XOR x\)

数据范围
\(1\leq n \leq 2*10^5\)
\(0\leq a_i \leq 2^30\)

思路

  • b[i]=a[i+k]^X -> b[i]^a[i+k] = X = b[i+1]^a[i+k+1] -> b[i] ^ b[i + 1] = a[i+k] ^ a[i+k+1]
  • 转化后对 \(a,b\) 作差分, 答案转化为 \(b\) 在 \(a\) 中的出现次数, 就是有多少个 \(k\), 对于每个 \(k\) 的 \(x\) 很好求
  • 显然将 \(b\) 作为模板串需要将 \(a\) 拉成环再破成链
  • 最后做一次 KMP 就可以求解

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define mkp make_pair
#define endl "\n"
using namespace std;
const int N = 2e5 + 10;
int ne[2 * N], n, ans = 0;

void get_ne(vector<int>& s){
	int len = 2 * n;
	ne[1] = 0;
	for(int i = 2, j = 0; i <= len; i++){
		while(j && s[j + 1] != s[i]) j = ne[j];
		if(s[i] == s[j + 1]) j++;
		ne[i] = j;
	}
}

vector<int> Match(vector<int>& s, vector<int>& p){		
	int lens = 2 * n - 1, lenp = n;
	vector<int> res;
	for(int i = 1, j = 0; i <= lens; i++){
		while(j && s[i] != p[j + 1]) j = ne[j];
		if(s[i] == p[j + 1]) j++;
		if(j == lenp){
			res.pb(i - n);
			ans++;
			j = ne[j];
		}
	}
	return res;
}

int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin >> n;
	vector<int> a(2 * n + 1, 0), xa(n + 1, 0);
	vector<int> b(n + 1, 0), xb(n + 1, 0);
	for(int i = 1; i <= n; i++)
		cin >> xa[i];
	for(int i = 1; i <= n; i++)
		cin >> xb[i];
	for(int i = 1; i <= n; i++){
		if(i != n){
			a[i] = xa[i] ^xa[i + 1];
			b[i] = xb[i] ^ xb[i + 1];
		}
		else{
			a[i] = xa[i] ^ xa[1];
			b[i] = xb[i] ^ xb[1];
		}
		a[i + n] = a[i];
	}
	get_ne(a);
	auto res = Match(a, b);
	for(auto k: res){
		cout << k << " " << (xb[1] ^ xa[1 + k]) << endl;
	}
    return 0;
}

ABC 151

E. Max-Min Sums (组合计数, 贡献, 排序)

题意

定义 \(X\) 为一个 \(k\) 个数的集合, \(f(X) = max{X} - min{X}\) , 求长度为 \(n\) 的数列 \(A\), 所有大小为 \(k\) 的 \(f(X)\) 总和.

数据范围
\(1\leq N \leq 10^5\)

思路

  • 考虑按贡献计算, 对于每一个数作为最大数, 意味小于等于他的树必须有 \(k-1\) 个, 最小数同理
  • 务必将数组按大小排序, 然后扫一遍, 预处理阶乘, 逆元计算组合数, 这样能不重不漏计算

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define mkp make_pair
#define endl "\n"
using namespace std;
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
ll fact[N], inv[N];

vector<int> alls;

ll qmi(ll a, ll k, int mod){
	ll res = 1;
	while(k){
		if(k & 1)
			res = res * a % mod;
		a = a * a % mod;
		k >>= 1;
	}
	return res;
}

int find(int x){
	return lower_bound(arr(alls), x) - alls.begin() + 1;
}

ll C(ll a, ll b){
	if(a < b) return 0;
	return fact[a] * inv[b] % mod * inv[a - b] % mod;
}

int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int n, k;
	cin >> n >> k;
	vector<ll> a(n + 1, 0);
	fact[0] = 1;
	inv[0] = 1;
	for(int i = 1; i <= n; i++){
		fact[i] = fact[i - 1] * i % mod;
		inv[i] = qmi(fact[i], mod - 2, mod);
	}
	for(int i = 1; i <= n; i++){
		cin >> a[i];
	}
	sort(a.begin() + 1, a.end());
	ll ans = 0;
	for(int i = 1; i <= n; i++){
		ans = (ans + C(i - 1, k - 1) * a[i] % mod) % mod;
		ans = (ans - C(n - i, k - 1) * a[i] % mod + mod) % mod;
	}
	cout << ans << endl;
    return 0;
}

F. Enclose All (待补, 最小圆覆盖)

题意

求覆盖所有点的最小圆的半径

ABC 152

vp, AC了 ABCDE, D题写的有点蠢, E题 Python 水过

D. Handstand 2 (思维暴力模拟)

题意

输入 \(n\) , 找合法的数对 \((A,B)\) 个数, 合法数对定义: \(A\) 的首数位和 \(B\) 的末数位相同, \(B\) 的首数位和 \(A\) 的末数位相同, 不能有前导零

数据范围
\(1\leq n\leq 2 * 10^5\)

思路

  • 自己写的分类讨论, 约等于没做出来这个题, 换一种更简单的思路
  • 设 \(c[i][j]\) 表示首位为 \(i\) , 末位为 \(j\) 的数字个数, 可以 \(O(n)\) 预处理获得
  • 符合答案的数对个数就是 \(\Sigma_{i=0}^9\Sigma_{j=0}^9 c[i][j]\)

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define mkp make_pair
#define endl "\n"
using namespace std;
const int N = 10;
ll c[N][N];

int getlen(int x){
	if(x >= 100000) return 6;
	if(x >= 10000) return 5;
	if(x >= 1000) return 4;
	if(x >= 100) return 3;
	if(x >= 10)	return 2;
	return 1;
}

int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int n;
	cin >> n;
	int mi[8];
	mi[0] = 1;
	for(int i = 1; i <= 6; i++)
		mi[i] = mi[i - 1] * 10;
	for(int i = 1; i <= n; i++){
		int len = getlen(i);
		int l = i / mi[len - 1], r = i % 10;
		c[l][r] ++;
	}
	ll ans = 0;
	for(int i = 0; i <= 9; i++)
		for(int j = 0; j <= 9; j++)
			ans += c[i][j] * c[j][i];
	cout << ans << endl;
    return 0;
}

E. Flatten (质因子分解求LCM)

题意

简化一下, 求 \(n\) 个数的 \(LCM\) , 累加 \(LCM / a_i\) , 对 \(1e9+7\) 取模

数据范围

  • \(1\leq n \leq 10^4\)
  • \(1\leq A_i \leq 10^6\)

思路

  • \(LCM\) 太大, 用质因数分解求 \(LCM\)
  • 所有 \(a_i\) 都能整除 LCM, 那么 LCM 势必包含所有 \(a_i\) 的质因子, 对每个 \(a_i\) 的质因子取最大指数即可
  • 最后用快速幂求逆元处理一下就好

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define endl "\n"
using namespace std;
const int mod = 1e9 + 7;
const int N = 1e6 + 10;
int	cnt[N];

ll gcd(ll a, ll b){
	return b ? gcd(b, a % b) : a;
}

ll qmi(ll a, ll k, int mod){
	ll res = 1;
	while(k){
		if(k & 1)
			res = res * a % mod;
		a = a * a % mod;
		k >>= 1;
	}
	return res;
}

int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int n;
	cin >> n;
	vector<int> a(n);
	int mx = 0;
	for(int i = 0; i < n; i++){
		cin >> a[i];
		int x = a[i];
		mx = max(mx, x);
	for(int j = 2; j <= x / j; j++){
			int t = 0;
			while(x % j == 0){
				t++;
				x /= j;
			}
			cnt[j] = max(cnt[j], t);
		}
		if(x)
			cnt[x] = max(cnt[x], 1);
	}
	ll lcm = 1;
	for(int i = 1; i <= mx; i++)
		lcm = lcm * qmi(i, cnt[i], mod) % mod;
	ll sum = 0;
	for(int i = 0; i < n; i++){
		sum = (sum + lcm * qmi(a[i], mod - 2, mod) % mod) % mod;
	}
	cout << sum << endl;
    return 0;
}	

F. Tree and Constraints (容斥原理, 树上路径状态压缩)

__builtin_popcount 只能处理32位的数

题意

给定一棵树,把每条边染成黑色或白色,有 \(m\) 个限制,限制了 \(u→v\) 的路径上必须有至少一个黑色,求满足所有限制方案数

数据范围
\(2\leq N\leq 50\)
\(1\leq a_i,b_i\leq N\)
\(1\leq M \leq min(20, \frac{N(N-1)}{2})\)

思路

  • 正难则反, 把满足所有路径上至少涂一个黑色转化为, 求其中至少一个限制上的路径全是白色的方案数
  • 转化后发现是一个经典的容斥问题, 再观察 \(m\) 的范围确实可行, 对于不满足限制的条件, 路径全为白色, 两点路径外的边染色随意
  • 设和不满足限制的条件中的边数总共为 \(C\) 条, 则对应有 \(2^{n-1-C}\) 的方案数
  • 关于如何统计枚举到的每个状态的边数 \(C\) , 观察由于 \(N\leq 50\), 所以直接用邻接矩阵存边(方便用指向父节点的边)
  • 总共最多 50 条边, 可以开 longlong 来进行状态压缩, 对于每个限制预处理出路径上会出现的边, 状压上去, 多个限制直接取或即可.
  • 最后由于是求的目标答案的补, 还需要 1ll<<(n-1) - ans

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define mkp make_pair
#define endl "\n"
using namespace std;
const int N = 55;
ll g[N][N], n, m, fa[N], dep[N];
ll sta[N];
PII cond[N];

int popcount(ll x) { return x ? (popcount(x & (x-1)) + 1) : 0; }

void dfs(int u){
	dep[u] = dep[fa[u]] + 1;
	for(int v = 1; v <= n; v++){
		if(v == fa[u] || g[u][v] == -1) continue;
		fa[v] = u;
		dfs(v);
	}
}

void color(ll& s, int a, int b){
	if(dep[a] < dep[b])
		swap(a, b);
	while(dep[a] > dep[b]){
		s |= 1ll << g[a][fa[a]];
		a = fa[a];
	}
	if(a == b) return;
	whie(a != b){
		s |= 1ll << g[a][fa[a]], s |= 1ll << g[b][fa[b]];
		a = fa[a], b = fa[b];
	}
}

int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	memset(g, -1, sizeof g);
	cin >> n;
	for(int i = 1; i < n; i++){
		int a, b;
		cin >> a >> b;
		g[a][b] = g[b][a] = i - 1;
	}
	cin >> m;
	dfs(1);
	for(int i = 0; i < m; i++){
		int u, v;
		cin >> u >> v;
		color(sta[i], u, v);
	}
	ll ans = 0;
	for(int i = 1; i < 1ll << m; i++){
		ll s = 0;
		for(int j = 0; j < m; j++)
			if(i >> j & 1)
				s |= sta[j];
		int cnt = popcount(i), t = popcount(s);
		ll res = 1ll << (n - 1 - t);
		if(cnt & 1) ans += res;
		else ans -= res;
	}
	cout << (1ll << (n - 1)) - ans << endl;
    return 0;
}

ABC 153

ABCDE都没什么意思

F. Silver Fox vs Monster (贪心, 维护区间减, 差分/线段树)

题意

给了 \(N\) 个怪兽, 一次攻击可以让 \([x-d,x+d]\) 范围内怪兽受到 \(A\) 伤害, 每只怪兽有初始生命值 \(H_i\) , 询问消灭所有怪物的最小攻击次数

数据范围
\(N\leq 2*10^5\)
\(1\leq A, H_i\leq 10^9\)
\(0\leq D_i, X_i\leq 10^9\)

思路

  • 由于每次攻击可以转换为 \([x, x + 2*d]\) 范围, 所以如果从两端任意一端开始考虑的话, 是一个贪心问题.
  • 对于最左端而言, 想要消灭他, 一定要覆盖他, 那么我们自然想要覆盖地更远, 证明相较于覆盖地更近, 覆盖更远结果不会变差.
  • 那么只需要从左往右扫, 遇见非0就找到尽量远的点(使用二分), 然后维护区间减操作, 有两种做法线段树或者差分来维护.
  • 线段树相对无脑不再叙述, 主要说下差分的思路.

Solution

// 线段树做法
#include<bits/stdc++.h>
typedef long long ll;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define x first
#define y second
#define endl "\n"
using namespace std;
const int N = 2e5 + 10;
int n;
PLL a[N];

struct SegTree{
    #define ls u << 1
    #define rs u << 1 | 1
    struct T{
        int l, r;
        ll v;
        ll add;
        void init(){
        	v = a[l].y;
        	add = 0;
        }
    };
    vector<T> tr;
    SegTree() {}
    SegTree(int n): tr((n + 1) << 2) {build(1, 1, n);};
    void pushup(int u){
    	tr[u].v = tr[ls].v + tr[rs].v;
    }
    void update(T& rt, ll add){
    	rt.v += add * (rt.r - rt.l + 1);
    	rt.add += add;
    }
    void pushdown(int u){
    	if(tr[u].add){
    		update(tr[ls], tr[u].add);
    		update(tr[rs], tr[u].add);
    		tr[u].add = 0;
    	}
    }
    void build(int u, int l, int r){        // 建立线段树,(节点编号,节点区间左端点,节点区间右端点)
        tr[u] = (T){l, r}; 
        if(l == r){
            tr[u].init();
            return;
        }
        int mid = (l + r) >> 1;
        build(ls, l, mid), build(rs, mid + 1, r);       
        pushup(u);
    }   
    ll query(int u, int l, int r){
        if(tr[u].l >= l && tr[u].r <= r){
        	return tr[u].v;
        }  
        else{
            pushdown(u);        // 递归分裂前pushdown
            int mid = (tr[u].l + tr[u].r) >> 1;
            ll res = 0;
            if(l <= mid) res = query(ls, l, r);
            if(r > mid) res += query(rs, l, r);
            return res;
        }
    }
    void modify(int u, int l, int r, ll v){    // 区间修改
        if(tr[u].l >= l && tr[u].r <= r){       // 注意区间修改的递归出口
        	update(tr[u], v);
            return ;
        }
        pushdown(u);        // 递归分裂前 pushdown  
        int mid = (tr[u].l + tr[u].r) >> 1;
        if(l <= mid) modify(ls, l, r, v);
        if(r > mid) modify(rs, l, r, v);
        pushup(u);
    }
};

ll find(ll x){
	ll l = 0, r = n;
	while(l < r){
		ll mid = (l + r + 1) >> 1;
		if(a[mid].x <= x) l = mid;
		else r = mid - 1;
	}
	return l;
}

int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	ll d, A;
	cin >> n >> d >> A;
	for(int i = 1; i <= n; i++)
		cin >> a[i].x >> a[i].y;
	sort(a + 1, a + 1 + n);
	SegTree tr(n);
	tr.build(1, 1, n);
	ll ans = 0;
	for(int i = 1; i <= n; i++){
		ll res = tr.query(1, i, i);
		if(tr.query(1, i, i) > 0){
			int r = find(a[i].x + 2 * d);
			tr.modify(1, i, r, -(res + A - 1) / A * A);
			ans += (res + A - 1) / A;
		}
	}
	cout << ans << endl;
    return 0;
}
// 差分做法

ABC 154

E. Almost Everywhere Zero (简单数位DP)

题意

求 \(1-n\) 中, 非零数位恰好有 \(k\) 个的数字个数

数据范围
\(1\leq n \leq 10^{100}\)

思路

  • 一眼数位DP, 常规状态记录, \(dp[i][j]\) 搜到了第 \(i\) 位, 非零个数位 \(j\) 的数字个数

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define mkp make_pair
#define endl "\n"
using namespace std;
const int N = 110;
ll dp[N][4], a[N];
int k;

ll dfs(int pos, int cnt, bool lead, bool limit){
	if(cnt > k) return 0;
	if(pos == -1) return cnt == k;	// 最后判断符合条件需要严格 k 个数
	if(!limit && !lead && dp[pos][cnt] != -1) return dp[pos][cnt];
	int up = limit ? a[pos] : 9;
	if(cnt == k) up = 0;
	ll res = 0;
	for(int i = 0; i <= up; i++){
		int t = (i != 0);
		res += dfs(pos - 1, cnt + t, lead && i == 0, limit && i == a[pos]);
	}
	if(!limit && !lead) dp[pos][cnt] = res;
	return res;
}

int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	string s;
	cin >> s >> k;
	int pos = 0;
	memset(dp, -1, sizeof dp);
	for(int i = s.size() - 1; ~i; i--)
		a[pos++] = s[i] - '0';
	cout << dfs(pos - 1, 0, true, true) << endl;
    return 0;
}

F. Many Many Paths (组合数)

题意

二维平面, 输入两个点的坐标 \((r1, c1), (r2, c2)\), 求从 \((0,0)\) 到两点之间矩形区域内的点的路径数总和, 设 \(f(i,j)\) 为到 (i,j) 路径数, 则求 \(\Sigma_{r_1\leq i\leq r_2,c_1\leq j\leq c_2} f(i,j)\) 大小

数据范围
\(1\leq r1\leq r2\leq 10^6\)
\(1\leq c1\leq c2\leq 10^6\)

思路

  • 用二维前缀和容斥的思想, 如果我们知道 \(g(r,c)\) 代表 \((0,0)\) 到 \((r,c)\) 的方案和, 则答案等于 \(g(r_2,c_2)-g(r_1-1,c_2)-g(r2, c_1-1) + g(r_1-1,c_1-1)\)
  • 明显可知 \(f(i,j)=C_{i+j}^i\), 则需要求 \(\Sigma_{i=r_1}^{r_2} \Sigma_{j=c_1}^{c_2} C_{i+j}^j\), 考虑如何求单个 \(g(r,c)\) 就可以解决此题
  • 即便预处理逆元和阶乘, 也需要 \(O(n^2)\) 的复杂度, 考虑优化计算
  • 有公式 \(C_{m+r+1}^r = \Sigma_{i=0}^r C_{m+i}^i\) , 在此题意义便是 \(f(r,0)+f(r,1)+...+f(r,c)=f(r+1,c)\)
  • 因此内层循环被优化掉, 具体推导可以见代码注释部分, \(O(n)\) 求解各个 \(g(r,c)\)

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define mkp make_pair
#define endl "\n"
using namespace std;
const int mod = 1e9 + 7;
const int N = 2e6;
ll fact[N + 10], f1[N], f2[N], g1[N], g2[N];
ll inv[N + 10];

ll qmi(ll a, ll k, int mod) {
	ll res = 1;
	while (k) {
			if (k & 1)
					res = res * a % mod;
			a = a * a % mod;
			k >>= 1;
	}
	return res;
}

// ans = g(r2, c2) - g(r2, c1 - 1) - g(r1 - 1, c2) + g(r1 - 1, c1 - 1);	
// g(r, c) = f(0,0) +...+ f(0, c) + ... + f(r,0) + ... + f(r, c);
// g(r, c) = f(1,c) + f(2, c) + f(3,c) +...+ f(r+1,c)
// f(r,c) = (r+c)! * inv[r] * inv[c];

ll C(int a, int b) {
	return fact[a] * inv[b] % mod * inv[a - b] % mod;
}

int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int r1, r2, c1, c2;
	cin >> r1 >> c1 >> r2 >> c2;
	fact[0] = inv[0] = 1;
	for (int i = 1; i <= N + 5; i++) {
		fact[i] = 1ll * fact[i - 1] * i % mod;
		inv[i] = inv[i - 1] * qmi(i, mod - 2, mod) % mod;
	}
	ll g1 = 1, g2 = 1, g3 = 1, g4 = 1;
	for (int i = 1; i <= r2 + 1; i++) {
		g1 = (g1 + C(i + c2, c2)) % mod;
		g2 = (g2 + C(i + c1 - 1, c1 - 1)) % mod;
	}
	for (int i = 1; i <= r1; i++) {
		g3 = (g3 + C(i + c2, c2)) % mod;
		g4 = (g4 + C(i + c1 - 1, c1 - 1)) % mod;
	}
	cout << (g1 - g2 - g3 + g4 + mod) % mod << endl;
    return 0;
}

ABC 155

D. Pairs (二分套二分)

题意

Candy 手上有 \(N\) 个糖果,每个糖果有一个甜蜜值 \(A_i\) ( \(A_i\) 可为负数), Candy喜欢把糖果成双成对的摆在一起,当糖果 i 和糖果 j 在一起后,甜蜜值就会变成 \(A_i \times A_j\) ,而 \(N\) 个糖果总共有 \(N *(N - 1)/ 2\) 种配对方法,
Candy 想知道所有两两配对的糖果里,第 \(K\) 小的甜蜜值是多少?

数据范围
所有数值都是整数

\(2 \leq N \leq 2\times 10^5\)
\(1 \leq K \leq N * (N - 1) / 2\)
\(- 10^9 \leq A_i \leq 10^9, \;(1 \leq i \leq N)\)

思路

  • 二分答案里面再来一个二分
  • 显然要对 \(A\) 排序,main函数里二分答案,第 \(k\) 小的值是多少, 在 check 里面再二分
  • 循环 \(A_i\) , 然后观察
    • 如果 \(A_i < 0\),往右做乘积会变小,往左做乘积会变大。
    • 如果 \(A_i >= 0\),往左做乘积会变小,往右做乘积会变大。
  • 依据上述单调性在 check() 里面再套一个二分,复杂度为 \(O(n*logn*logn)\)
  • 需要补: 我的二分写法有点问题, 借了别人的二分写法才能过,有时间再改下

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define endl "\n"
using namespace std;
*----------------------------------------------------------------------------------------------------*/
const int N = 1e6 + 10;
ll a[N], n;

// ll check(ll x) {
//     ll res = 0;
//     for (int i = 0; i < n; i++) {
//         if (a[i] < 0) {
//             ll l = i, r = n;
//             while (l < r) {
//                 ll mid = (l + r) >> 1;
//                 if (a[i] * a[mid] <= x) r = mid;
//                 else l = mid + 1;
//             }
//             res += n - r;
//         }
//         else {
//             ll l = i, r = n;
//             while (l < r) {
//                 ll mid = (l + r + 1) >> 1;
//                 if (a[i] * a[mid] <= x) l = mid;
//                 else r = mid - 1;
//             }
//             res += l - i;
//         }
//     }
//     return res;
// }

ll check(ll x){
	ll res=0;
	for(int i=0;i<n;i++){
		if(a[i]<0){
			ll L=i,R=n;
			while(R-L>1){
				ll mid=(L+R)>>1;
				if(a[i]*a[mid]<=x){//这里要注意和下面不一样的是 当a[i]<0的时候mid取小 乘积反而会增大
					R=mid;
				}else{
					L=mid;
				}
			}
			res+=n-R;
		}else{
			ll L=i,R=n;
			while(R-L>1){
				ll mid=(L+R)>>1;
				if(a[i]*a[mid]<=x){//a[i]>0 mid取大 乘积增大
					L=mid;
				}else{
					R=mid;
				}
			}
			res+=L-i;
		}
	}
	return res;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    ll k;
    cin >> n >> k;
    for (int i = 0; i < n; i++)
        cin >> a[i];
    sort(a, a + n);
    ll l = -1e18, r = 1e18;
    while (r - l > 1) {
        ll mid = (l + r) >> 1;
        ll res = check(mid);
        if (res < k) l = mid;
        else r = mid;
    }
    cout << r << endl;
    return 0;
}

E. Payment (线性DP, 贪心)

题意

小王同学cf想上黑红,但自己太菜了,上不去黑红,于是他找到了出题人想要进行一波"交易",该场比赛需要小王支付 \(n\) 元才能拿到标程,小王和出题人的口袋里都只有 \(10^x\) 面额的钱( \(x\) 是个非负整数,每张货币都有无限个)。
为了保持交易的隐蔽性,他们要用最少几张货币恰好把账结清。

数据范围
\(1\leq x \leq 10^6\)

思路

  • 容易看出来有一点贪心的思路:
    • 如果某一位 \(\leq 4\) , 直接支付更优
    • 如果某一位 \(\geq 6\) , 支付一张更大的钱币找零,方案更优
    • 如果某一位 \(= 5\) , 直接支付是一种选择,花费为 \(5\),但如果下一位 \(\geq 5\),可以减小下一位的花费,即便需要多支付一张, 实际对这一位的贡献没有影响, 这种情况给下一位加 \(1\) 一定是一个更优解。
  • 最后计算一下多出来的一位的贡献,代码中多循环一次,用 dp 状态转移的方式实现

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define endl "\n"
using namespace std;

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    string s;
    cin >> s;
    reverse(arr(s));
    int n = s.size();
    vector<int> f(n + 2, 0);
    ll ans = 0;
    for (int i = 0; i < n; i++)
        f[i] = s[i] - '0';
    for (int i = 0; i <= n; i++) {      // 从低往高, f[i] 表示付清这一位的最小花费,进位的贡献留到下一位再算,如果 f[i] == 5 可以选择进位或者不进位
        if (f[i] == 10) {       // the if condition can be f[i] >= 10
            f[i] -= 10;
            f[i + 1] ++;
        }
        if (f[i] == 5) {
            ans += 5;
            if (f[i + 1] >= 5)
                f[i + 1] ++;
        }
        else if (f[i] > 5) {
            f[i + 1] ++;
            ans += 10 - f[i];
        }
        else    
            ans += f[i];
    }
    cout << ans << endl;
    return 0;
}

F. Perils in Parallel (异或差分,树上异或问题)

题意

给了 \(N\) 个地雷的位置 \(A_i\) 和状态 \(B_i\), \(B_i=1\) 为激活,反之未激活。有 \(M\) 次操作可以选择但只能选择一次。每次操作有区间 \(L_i,R_i\),操作将区间内的地雷状态翻转。询问有没有可能将所有地雷未激活,如果没有输出 \(-1\)

数据范围
\(1\leq N\leq 10^5\)
\(1\leq M\leq 2*10^5\)
\(A_i \neq A_j\)
\(1 \leq L_i \leq R_i \leq 10^9\)
\(1 \leq A_i \leq 10^9\)

思路

  • 显然先考虑将每个地雷按坐标排序
  • 即使这是一个区间操作问题,但没有数据结构题目的特征,没有什么入手的地方
  • 但对于区间操作一直有个很 useful 的东西叫 差分,这里我觉得即便想不到,也可以尝试硬找下方法试试
  • 而如何将区间翻转的操作对应到差分操作上,需要对原数组做 异或差分
    • b[i] = a[i] ^ a[i - 1], 这样对区间 l, r 的操作就是将 b[l]^=1, b[r+1]^=1,找到对应的 l,r 需要二分查找
    • 将区间操作映射到单点修改上之后,要考虑怎么样才能使地雷全部未激活。
    • 考虑一个等价状态: b[i] 全为0 <-> a[i] 全为0, 因为 b[i] = 1 代表 a[i] != a[i-1],势必有一个 a[i]==1
  • 因为区间操作改为单点修改实质上也是对 b[] 的操作,所以把问题转换为有 \(M\) 对操作让这对数(b[l],b[r+1])翻转,询问是否最后能全为0
    • 把问题建图,每个操作相当于一条边,最后形成若干连通块,点权是 \(b[i]\)。
    • 一个结论:每个连通块中 \(1\) 的个数为偶数时,才有合法答案。如果一个连通块中有奇数个 \(1\)
      • 证明:因为每次操作不会改变连通块的结果,只有若干次操作才行,考虑两点之间的路径,每条路径是将左右端点翻转,恰好路径两端点翻转了 1 次而中间的点都翻转了 2 没有变化,想要每个 1 都翻转为 0,因为每次操作只能改变偶数个 1 的状态,所以连通块内有偶数个 1 才合法,证毕
    • 然后就用 dfs() 实现即可,实现方式很巧妙,多巩固。

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define endl "\n"
using namespace std;
/*----------------------------------------------------------------------------------------------------*/
int n, m, ans;
const int N = 1e5 + 10;
int val[N], b[N], used[2 * N];
PII a[N];
vector<PII> edge[N];
vector<int> alls;
bool st[N];

int findl(int x) {
    return lower_bound(val + 1, val + 1 + n, x) - val;
}

int findr(int x) {
    return upper_bound(val + 1, val + 1 + n, x) - val;
}

int dfs(int u) {
    st[u] = true;
    int x = b[u];
    for (auto v: edge[u]) {
        if (st[v.x]) continue;
        if (dfs(v.x)) {
            ans ++;
            used[v.y] = true;
            x ^= 1;
        }
    }
    return x;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) 
        cin >> a[i].x >> a[i].y;
    sort(a + 1, a + 1 + n);
    for (int i = 1; i <= n; i++)
        val[i] = a[i].x;
    for (int i = 1; i <= n + 1; i ++){
        if (a[i].y == a[i - 1].y)
            b[i] = 0;
        else    
            b[i] = 1;
    }
    for (int i = 1; i <= m; i++) {
        int l, r;
        cin >> l >> r;
        l = findl(l), r = findr(r);
        if (l == r) continue;
        edge[l].pb({r, i}), edge[r].pb({l, i});
    }
    for (int i = 1; i <= n; i++) {
        if (!st[i] && dfs(i)) {
            cout << "-1\n";
            return 0;
        }
    }
    cout << ans << endl;
    for (int i = 1; i <= m; i++)
        if(used[i])
            cout << i << " ";
    return 0;
}

ABC 156

D. Bouquet (朴素求组合数,补集思想)

题意
从 \(n\) 种花里挑,每种花只有一个,最后花的数量不能是 \(a\) 或者 \(b\),求所有方案数 \(\mod 1e9+7\)

数据范围
\(2\leq n \leq 10^9\)
\(1\leq a < b \leq min(n, 2 * 10^5)\)

思路

  • 很容易想到用所有的方案数 - 选 \(a\) 个和 \(b\) 个的方案数
  • \(n\) 太大怎么办? 发现 \(a\) 和 \(b\) 都在 \(2e5\) 范围内,由组合数公式 \(C_n^r = \frac{n!}{r!*(n-r)!}\), 可以在 \(O(a)\) 时间内求出
  • 预处理阶乘和逆元即可

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define endl "\n"
using namespace std;
/*----------------------------------------------------------------------------------------------------*/
const int mod = 1e9 + 7;

ll qmi(ll a, ll k, int mod) {
    ll res = 1;
    while (k) {
            if (k & 1)
                    res = res * a % mod;
            a = a * a % mod;
            k >>= 1;
    }
    return res;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    ll n, a, b;
    cin >> n >> a >> b;
    ll c1 = 1, c2 = 1;
    for (int i = 1; i <= a; i ++) {
        c1 = c1 * (n - a + i) % mod * qmi(i, mod - 2, mod) % mod;
    }
    for (int i = 1; i <= b; i ++) {
        c2 = c2 * (n - b + i) % mod * qmi(i, mod - 2, mod) % mod;
    }

    cout << (qmi(2, n, mod) - c1 - c2 + 2 * mod - 1 ) % mod << endl;
    return 0;
}

E. Roaming (组合计数,隔板法)

题意

有 \(n\) 个房间,每个房间 \(1\) 个人,定义一个事件是某个人在 \(n\) 个房间中任意移动一次,但不能待在原地, 询问 \(k\) 次事件后,各房间有多少种情况,答案 \(\mod 1e9+ 7\)

数据范围
\(3\leq n \leq 2 * 10^5\)
\(2\leq k \leq 10^9\)

思路

  • 首先题目可以构造出所有房间仍然是 \(1\) 个人的情况,形成环即可。并且题目并不关心人的编号,只关心各房间情况
  • 先划分子集,按照最后情况中人数为 \(0\) 的房间的个数进行划分。
  • 假设有 \(m\) 个房间为空, 那么要在 \(n-m\) 个房间内塞 \(n\) 个人且不为空。
  • 问题等价于,对于 \(n\) 个小球中间的 \(n-1\) 个位置放置 \(n-m-1\) 个隔板,形成 \(n-m\) 个不为空的间隔的方案数。
  • 选择 \(m\) 个房间方案数为 \(C_n^m\) ,放置隔板方案数为 \(C_{n-1}^{n-m-1}\)

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define endl "\n"
using namespace std;
/*----------------------------------------------------------------------------------------------------*/
const int N = 2e5 + 10, mod = 1e9 + 7;
ll fact[N], inv[N], n, k;

ll qmi(ll a, ll k, int mod) {
    ll res = 1;
    while (k) {
            if (k & 1)
                    res = res * a % mod;
            a = a * a % mod;
            k >>= 1;
    }
    return res;
}

ll C (int a, int b) {
    if (a < b) return 0;
    return fact[a] * inv[b] % mod * inv[a - b] % mod;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> k;
    ll ans = 0;
    fact[0] = 1;
    inv[0] = 1;
    for (int i = 1; i <= n; i++) {
        fact[i] = fact[i - 1] * i % mod;
        inv[i] = qmi(fact[i], mod - 2, mod);
    }
    for (int i = 0; i <= min(n - 1, k); i++) {
        ans = (ans + C(n, i) * C(n - 1, n - i - 1) % mod) % mod;
    }
    cout << ans << endl;
    return 0;
}

F. Modularness (补集,用商判断两数模意义下的大小)

题意

给了长度为 \(k\) 的数组 \(d\),下标从 0 开始。进行 \(q\) 次询问。
每次询问输入 \(n, x, m\), 有长度为 \(n\) 的数组 \(a\),下标从 0 开始。
其中:

\[ a_j = \begin{cases} x & \text{j = 0} \\ a_{j-1}+d_{{j-1}\mod m} & \text{0 < j < n} \end{cases} \]

对于每次询问,输出有多少 \(j\;(0<j<n-1)\) 满足 \((a_j\mod m) < (a_{j+1}\mod m)\)

数据范围
\(1\leq k,q \leq 5000\)
\(0\leq d_i \leq 10^9\)
\(2\leq n \leq 10^9\)
\(0\leq x \leq 10^9\)
\(2\leq m \leq 10^9\)

思路

  • 从题目可以看出每个 \(a_i\) 是 \(d\) 的环形前缀和,并且对于每次询问将 \(d_i \% m\) 并不会影响答案。
  • 对于相邻的 \(a_i, a_{i+1}\) 其实只差一个 \(d_i\) ,如果对答案有贡献则表明 \((a_i + d_i) / m\) 没有增加。
  • 然而正面计算其实很困难,考虑求补集,即 \((a_j\mod m) >= (a_{j+1}\mod m)\)
    • 对于 \((a_j\mod m) = (a_{j+1}\mod m)\) 的情况, 只需要关心 \(d_i=0\) 的地方即可
    • 对于 \((a_j\mod m) > (a_{j+1}\mod m)\) 的情况,由于每个 \(d_i < m\) ,发生这种情况当且仅当 \(a_{j+1} / m = a_j / m + 1\)
      • 这个题的关键点就是这种局部的累积可以在末尾算出,即数量为 \(a_{n-1} / m - a_0/m = a_{n-1}m - x / m\)
      • \(a_{n-1}\) 是可以在 \(O(k)\) 时间内计算出来的,计算每个 \(d_i\) 的累加次数即可。注意计算 \(a_{n-1}\) 只需要进行 \(n-1\) 次加法,代码中的一个细节。
  • 时间复杂度 \(O(q*k)\)

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define endl "\n"
using namespace std;
/*----------------------------------------------------------------------------------------------------*/

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int k, q;
    cin >> k >> q;
    vector<ll> d(k, 0);
    for (int i = 0; i < k; i ++)
        cin >> d[i];
    while (q--) {
        int n, x, m;
        cin >> n >> x >> m;
        vector<ll> cd(k);
        int cnt = 0;
        for (int i = 0; i < k; i ++){
            cd[i] = d[i] % m;
        }
        ll a = x;
        n--;        // 实际进行 n - 1 次加法
        for (int i = 0; i < k; i++) {       // 计算补集
            a += (n / k) * cd[i];
            if (i < n % k)
                a += cd[i];
            if (!cd[i]) {   
                cnt += n / k;
                if (i < n % k)
                    cnt++;
            }
        }
        cnt += a / m - x / m;
        cout << n - cnt << endl;
    }
    return 0;
}

ABC 157

D. Friend Suggestions (连通块问题)

题意

给了 \(n\) 个点的图,\(m\) 条友好边, \(k\) 条 阻碍边, 没有重边自环。

求对于每个点,友好关系的点的个数,友好关系定义如下:

  1. 两点之间不能有友好边或者阻碍边
  2. 可以通过若干友好边到达
  3. 不能是自己

数据范围
\(2\leq N \leq 10^5\)
\(0\leq M \leq 10^5\)
\(0\leq K \leq 10^5\)
\(1\leq A_i,B_i \leq 10^5\)

思路

  • 对于每个点可能成为友好关系的点一定在友好边所连接的相同连通块中。
  • 把每个连通块的大小找出来,搜一下连通块内不满足其他条件的点数,就得到了答案。

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define endl "\n"
using namespace std;
/*----------------------------------------------------------------------------------------------------*/
const int N = 1e5 + 10;
vector<int> edgef[N], edgeb[N];
int p[N], n, m, k, id[N], idx, sz[N];

int dfs(int u, int p){
    int res = 1;
    id[u] = idx;
    for (auto v: edgef[u]) {
        if (id[v]) continue;
        res += dfs(v, u);
    }
    return res;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m >> k;
    for (int i = 0; i < m; i++) {
        int a, b;
        cin >> a >> b;
        edgef[a].pb(b);
        edgef[b].pb(a);
    }
    for (int i = 0; i < k; i++) {
        int a, b;
        cin >> a >> b;
        edgeb[a].pb(b);
        edgeb[b].pb(a);
    
    for (int i = 1; i <= n; i++) {
        if (!id[i]) {
            ++ idx;
            sz[idx] = dfs(i, -1);
        }
    }
    for (int i = 1; i <= n; i++) {
        int cnt = 0;
        for (auto v: edgef[i]) 
            if (id[v] == id[i])
                cnt ++;
        for (auto v: edgeb[i])
            if (id[v] == id[i])
                cnt ++;
        cout << sz[id[i]] - cnt - 1 << " ";
    }
    return 0;
}

E. Simple String Queries (区间查询不同数个数,Set,树状数组,线段树)

题意

给了长度为 \(n\) 的字符串,进行 \(q\) 次操作:

  1. 输入 1 pos x 将 pos 下标的字符改为 x
  2. 输入 2 l r, 输出 [l,r] 区间内不同字符个数。

数据范围
\(1\leq N \leq 5*10^5\)
\(1\leq Q \leq 20000\)

思路

  1. 将字符映射成大小为26的数组。
  2. 然后方法有很多,这里给出 set 的方法,还可以用树状数组和线段树(自己写的线段树,蠢极了)
  3. 建立 26 个 set, 维护每个字符的所有出现位置,修改很容易实现
  4. 每次查询,循环 26 个 set,对每个 set 进行 lower_bound(l), 如果值 <= r ,答案 + 1。

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define endl "\n"
using namespace std;
/*----------------------------------------------------------------------------------------------------*/

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    string s;
    int n, q;
    cin >> n >> s;
    vector<int> a(n + 1, 0);
    s = " " + s;
    set<int> S[26];
    for (int i = 1; i <= n; i++){
        a[i] = s[i] - 'a';
        S[a[i]].insert(i);
    }
    for (int i = 0; i < 26; i++) {
        S[i].insert(-1), S[i].insert(n + 1);
    }
    cin >> q;
    while (q--) {
        int op, pos, l, r;
        char c;
        cin >> op;
        if (op == 1) {
            cin >> pos >> c;
            int x = c - 'a';
            S[a[pos]].erase(pos);
            S[x].insert(pos);
            a[pos] = x;
        } else {
            cin >> l >> r;
            int res = 0;
            for (int i = 0; i < 26; i++) {
                if (*S[i].lower_bound(l) <= r) 
                    res ++;
            }
            cout << res << endl;
        }
    }
    return 0;
}

F. Yakiniku Optimization Problem (计算几何,未补)

ABC 250

E. Prefix Equality (整数哈希)

题意

给了两个长度为 \(n\) 的数组,判断两者前缀的集合是否相同(长度可以不一样)。

数据范围
\(1\leq N,Q\leq 2\times 10^5\)
\(1\leq a_i,b_i \leq 10^9\)
\(1\leq x_i,y_i \leq N\)

思路

  • 显然需要 std::set ,考虑两个 set 分别记录两个数组每个数是否出现
  • 用两个数组对两个 set 进行整数哈希h[i] 表示数组前 i 位上的哈希值,如果没有新的数出现 h[i] = h[i - 1].

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
#define x first
#define y second
#define pb push_back
#define mkp make_pair
#define endl "\n"
using namespace std;
unsigned seed1 = std::chrono::system_clock::now().time_since_epoch().count();
mt19937_64 rd(seed1);
const int mod = 1e9 + 7;

int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int n;
	cin >> n;
	vector<int> a(n + 1), b(n + 1);
	vector<unsigned int> h1(n + 1), h2(n + 1);
	for(int i = 1; i <= n; i++)
		cin >> a[i];
	for(int i = 1; i <= n; i++)
		cin >> b[i];
	set<int> s1, s2;
	h1[0] = h2[0] = 0;
	map<int,ull> mp;
	for(int i = 1; i <= n; i++){
		h1[i] = h1[i - 1];
		h2[i] = h2[i - 1];
		if(!mp.count(a[i])){
			mp[a[i]] = rd();
		}
		if(!s1.count(a[i]))
			h1[i] += mp[a[i]];
		if(!mp.count(b[i]))
			mp[b[i]] = rd();
		if(!s2.count(b[i]))
			h2[i] += mp[b[i]];
		s1.insert(a[i]);
		s2.insert(b[i]);
	}
	int q;
	cin >> q;
	while(q--){
		int x, y;
		cin >> x >> y;
		(h1[x] == h2[y]) ? cout << "Yes\n" : cout << "No\n";
	}
    return 0;
}

标签:AtCoder,typedef,int,题解,ll,long,leq,集合,define
来源: https://www.cnblogs.com/Roshin/p/AtCoder_notes.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有