AtCoder 题解集

虽然暂时不知道会不会从 XCPC 中退役,但还是想把这个题解集给维护下去。

\(created\; at\; 2022/6/24\; by\; Roshin\)

目录

AGC

ARC

ABC

ABC 138

F. Coincidence (结论,数位DP)

题意

找出 \(L,R(L\leq R\leq 10^{18})\) 中,\(x\leq y\) 满足 \(y\% x =y\;xor\; x\) 的 x,y 对数。

思路

  • 直接思考发现并不好做,对于 y % x:
    • 如果 \(2 * x > y\), 则 \(y \% x = y - x\)
    • 否则 \(y \% x < y - x\)
  • 而有个结论是 \(x \;xor\; y \geq x - y\)
  • 所以当 \(y\% x =y\;xor\; x\) 时,只有 \(2 * x > y\) 才能满足,即求 \(y-x=y\; xor\; x\)
  • 简单手摸可以发现,对于任意位 \(bit_y \geq bit_x\) ,并且 x ,y 位数相同最高位为 1
  • 设计状态 \(dp[pos][upy][downy][upx][downx]\) 可以求解答案,即维护 x,y 有没有在临界态上。

Solution

评测记录

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(b);
	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 158

E. Divisible Substring (取模前缀和思维, 一点点基本数论)

题意

给了一个长度为 \(n\) 的数字串,和一个质数 \(p\) ,询问有多少子串对应的数字满足是 \(p\) 的倍数,输出答案, 若有前导零也算作合法数字。

数据范围
\(1\leq N \leq 2 * 10^5\)
\(2\leq P \leq 10000\)

思路

  • 显然不能用高精,过于荒谬,然后我就不会了
  • 其实是个前缀和题目,但有个小结论:
    • \(x_1x_2x_3x_4x_5 * 10^n \% p = m,\;x_1x_2*10^{n+3}\%p=m\)
    • \(x_3x_4x_5 * 10^n \% p = 0\), 即 \(x_3x_4x_5 \% p = 0\)
    • \(p\) 是质数,且 \(p \neq 2 \& p \neq 5\)
    • \(p = 2\), \(p = 5\) 特判即可
  • 利用以上结论做前缀和计数即可,很经典,记得 cnt[0] = 0

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;
/*----------------------------------------------------------------------------------------------------*/

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, p;
    string s;
    cin >> n >> p >> s;
    ll sum = 0, ans = 0;
    vector<ll> cnt(p, 0);
    if (p == 2 || p == 5) {
        for (int i = n - 1; i >= 0; i--)
            ans = ans + ((s[i] - '0') % p == 0) * (i + 1);
    }
    else {
        cnt[0] = 1;
        for (int i = 0; i < n; i++) {
            sum = (sum * 10 + s[i] - '0') % p;
            ll now = qmi(10, n - 1 - i, p) * sum % p;
            ans += cnt[now];
            cnt[now]++;
        }
    }
    cout << ans << endl;
    return 0;
}
// x1x2x3x4 * 10^3 % p = m, x1x2 * 10^5 % p = m
// 两者相减, x3x4 * 10^3 % p = 0 等价于 x3x4 % p = 0

F. Removing Robots (线性DP,思维)

题意

给了 \(n\) 个机器人的坐标 \(x_i\) 和移动距离 \(d_i\), 每个机器人可以被主动激活向正轴移动 \(d_i\) 距离, 如果途中碰到别的机器人也会激活别的机器人, 以此递归进行。主动激活的次数不限制,机器人被激活后移动完成就会消失,询问最后机器人的剩余的不同情况数。

答案\(\mod 998244353\)

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

思路

  • 考虑问题的话,要逆向来考虑是显然的,正着太麻烦,并且主动激活的顺序和结果无关
  • DP的由来感觉不是很好想?定义状态 \(f[i]\) 为关于 [i,n] 机器人的方案数。
  • 如果这样定义状态的话,状态转移就好想了。对于此题对一个机器人的操作是要么主动激活要么被动激活要么没有激活。
    • 对于主动激活,不考虑前面的机器人的局面会是什么,那么对于后面的情况,局面是和激活该机器人能影响的范围有关(直接或者间接)。
    • 对于被动激活,不考虑前面机器人的局面,实际是和主动激活一个效果
    • 对于没有激活,不考虑前面机器人的局面,其实是后一个机器人的所有局面。
  • 所以状态转移:f[i] = (f[i+1] + f[next]) % mod, 目标答案 f[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 mod = 998244353;

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    vector<PII> v(n + 1);
    for (int i = 1; i <= n; i++){
        cin >> v[i].x >> v[i].y;
        v[i].y += v[i].x;
    }
    sort(v.begin() + 1, v.end());
    vector<ll> f(n + 2, 0);     // f[i] 代表 [i, n] 的方案数。主动激活由 right 转移, 不激活由 i + 1 转移
    f[n + 1] = 1;
    vector<int> stk(n + 2, 0);
    int top = 0;
    stk[++top] = n + 1;
    v.pb({2e9 + 10 ,0});
    for (int i = n; i ; i--) {
        while (top && v[stk[top]].x < v[i].y) top--;
        int right = max(stk[top], i);
        stk[++top] = i;
        f[i] = (f[i + 1] + f[right]) % mod;
    }
    cout << f[1] << endl;
    return 0;
}

ABC 159

E. Dividing Chocolate (二进制枚举,观察数据范围)

题意

\(H\times W\) 大小的方格矩阵,每个方格为 \(0\) or \(1\), 现在可以进行横切与竖切,询问最少切多少次可以保证最后的分块中每个分块都有不超过 \(K\)\(1\)

数据范围
\(1\leq H \leq 10\)
\(1\leq W \leq 1000\)
\(1\leq K \leq H\times W\)

思路

  • 观察数据范围, \(1\leq H \leq 10\) , 所以尝试枚举横切,复杂度 \(O(2^H)\)
  • 之后我们只关注竖切。从左往右依次遍历,贪心来切一定是最优的。
  • 总复杂度 \(O(2^H*H*W)\)$

评测记录

F. Knapsack for All Segments (非典型DP,DP优化)

题意

给了长度为 \(N\) 的数组 \(A\), 和一个正数 \(S\)
定义 \(f(L,R),1\leq L\leq R\leq N\) 为合法序列 \((x_1,x_2,...x_k)\) 的方案数:

\[L\leq x_1<x_2 <\cdots <x_k\leq R,\;A_{x_1}+A_{x_2}+\cdots+A_{x_k}=S \]

\(\Sigma f(L,R) \mod 998244353\)

数据范围
\(1\leq N,S,A_i \leq 3000\)

思路

  • 数据范围意味着我们可以接受 \(O(n^2)\) 的算法,并且如果 dp 的话大概率是可以把序列和 \(S\) 给放进状态里去的。
  • 考虑DP的话,很容易想出时空复杂度为 \(O(n^2*S)\) 的状态, \(f[l][r][x]\) 代表区间 \([l,r]\) 和位 \([x]\) 的方案数,转移可以 \(O(1)\) 类似区间DP的想法,但显然无论时间还是空间都爆掉了,考虑优化。
  • 按照题目意思,如果区间 \([l,r]\) 刚好能选出满足要求的数,对答案的贡献是 \((n-r+1)*l\), 由乘法原理得到。
  • 一个小trick, 算trick嘛?应该算。考虑去掉 l,r 其中一维,这里取掉其中 1 维。同时状态值的含义也要改变。将答案状态空间按照要选取的 \(x\) 序列最右端点为不同的 \(r\) ,进行不重不漏的划分。状态值改为记录贡献相关数值的形式来计算答案。
  • 状态表示: \(f[r][x]\) 表示最右端点为 \(r\) (不一定必选),左端点的下标和为 \(x\) 的方案数。普遍的来说,答案的表示则为 \(\Sigma_{i=1}^n f[i-1][s-a[i]]\) ,这样的方式能表达出每个点成为序列的最右端点的所有子集贡献。
  • 状态转移:
    • 枚举所有 \(0\leq k\leq s\)
    • 如果 \(a[i] < k\), \(f[i][k] = f[i - 1][k - a[i]] + f[i - 1][k]\)
    • 如果 \(a[i] == k\)\(f[i][k] = i + f[i - 1][k]\)
    • 如果 \(a[i] > k\) \(f[i][k] = f[i - 1][k]\)
  • 这道题答案不由状态直接表达,而是通过另一种方式间接计算,十分奇妙,值得多复习。时间复杂度为 \(O(s*n)\)

评测记录

ABC 160

F. Distributing Integers (树的拓扑序计数,换根DP)

题意

\(n\) 个节点的无根树,对树进行编号,先选定一个点 \(k\) 编号为1, 然后依次对剩余结点编号

有一颗节点编号为 \(1\)\(N\) 的树,第 \(i\) 条边连接点 \(a_i\)\(b_i\) 。对于 \(1\)\(N\) 的每个 \(k\) 进行如下操作:

  • 按如下操作在树上每个点写一个数字:
    • 在点 \(k\) 上写上 \(1\).
    • 按从 \(2\)\(N\) 的顺序将数写在节点上:
    • 选择一个仍未写有数字且与已写有数字的点相邻的点,如果有多个这样的点,随机选择一个
  • 输出所有写法的数量,结果模 \(1e9 + 7\)

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

思路

  • 看似编号计数,实际是树的拓扑序列计数。
  • 一个要点是,编号全排列是 \(n!\) ,然而注意一个子树中根节点必须先被染色,所以要 \(/size[u]\) ,故总方案数为 \(n! / \Pi size[u]\)
  • 由于是无根树,显然需要进行换根DP,先预处理每个点的子树大小,和子树的前缀积。换根时记录原根方向的前缀积。
  • 时间复杂度 \(O(n)\)

评测记录

ABC 168

E. ∙ (Bullet) (计数)

题意

给了 \(n\) 对数 a, b,现在取一个集合,集合中不能有两对数 i, j,满足 \(a_i*a_j + b_i*b_j = 0\)

数据范围

\(1 \leq n \leq 2 \times 10^5\)
\(-10^{18}\leq a,b \leq 10^{18}\)

思路

  • 先把 \(a_i,b_i\) 化为互质,这样 i, j 满足关系当且仅当 j 满足 \(a_j=-b_i,b_j=a_i\) 或者 \(a_j=b_i,b_j=-a_i\)
  • 然后对于满足这样互斥关系不存在传递性。答案从每对互斥关系中进行简单的计数。
  • 一些细节(被细节锤爆了),特殊的互斥 x 00 x (x 是不为 0 的数),对于 0 0 要么只选其中一个(全局),要么一个都不选,选其他类。

Solution

评测记录

F. . (Single Dot) (思维bfs,二维面积和一维线段问题)

题意

在平面上有分别 \(n,m\) 条竖横的栅栏坐标在 \(-10^9\leq x,y\leq 10^9\) 范围,牛在 (0, 0) 点,询问牛的活动面积(有可能是无穷)。

数据范围

\(1\leq n,m\leq 1000\)

思路

  • 先离散化所有坐标,这样形成了一个 \(n*m\) 的网格。
  • 然后对每个网格中的小格子(矩阵)取左上角为代表元素,那么可以用 bfs 来统计面积。
    • 向上移动:(x, y) 不在横着的栅栏上
    • 向下移动:(x+1, y) 不在横着的栅栏上 (不能统计对应栅栏下的面积)
    • 向左移动:(x, y) 不在竖着的栅栏上。
    • 向右移动:(x, y+1) 不在竖着的栅栏上。
  • 如何理解就是左上角为代表元,统计面积是 (row[x+1]-row[x]) * (col[y+1]-col[y]) 向下和向右移动目标点都会有限制,另外是可以在栅栏上移动的。
  • 统计无穷就是,超出了离散化的坐标范围。

Solution

评测记录

ABC 169

F. Knapsack for All Subsets (背包DP)

题意

定义 \(f(T)\) 为从集合 T 中选取非空子集,子集和为 \(S\) 的子集数量。求长度为 \(N\) 的序列 \(A\) 所有子集的 \(f(T)\) 之和。

数据范围

  • \(1\leq N,S,A_i\leq 3000\)

思路

  • 不能想复杂乐,按贡献来计算,对于某个子集和恰好满足要求,会对于包含它的所有子集贡献 + 1。
  • 而有一种常见的背包 dp 是,\(dp_{i,j}\) 表示前 \(i\) 个选和为 \(j\) 的集合数。而重要点就是这个题只需要改一下转移就好了。。。
  • \(dp_{i,j} = 2*dp_{i-1,j} + dp_{i,j-a_i}\),乘 2 的转移是选这个数但不计入和会产生一个新的集合,不选这个数就是继承原来的集合的答案。

Solution

评测记录

ABC 224

E. Integers on Grid (DP, 智慧实现)

题意

在二维方格上有 \(N\,(N\leq 2e5)\) 个点,每个点只能向和他同列或者同行且权值比他大的点移动,给了 \(N\) 个点坐标和权值,其余点权值为 \(0\) 。 询问每个点最多走多少次

思路

  • 有一个很暴力的想法是直接对每个点连边之后,拓扑排序求最长路。
  • 但发现连边数量是 \(n^2\) 的,不能接受。
  • 一个方法是可以对每一行每一列权值相同的点建立一个虚拟点,这样可以降低复杂度。
  • 题解的方法是,将所有权值相同的点放在一起,从大到小遍历,维护一个行最大值和列最大值,每到一个权值就更新所有点的 dp 值为其行所在最大值或列所在最大值。
  • 然后用这些点更新后的 dp 值再去更新行最大值和列最大值,非常的智慧。
const int N = 2e5 + 10;
int H, W, n;
int r[N], c[N], a[N];
int rmax[N], cmax[N], dp[N];
map<int, vector<int>> mp;

int main() {
    re(H), re(W), re(n);
    for (int i = 1; i <= n; i++) {
        re(r[i]), re(c[i]), re(a[i]);
        mp[a[i]].pb(i);
    }
    for (auto it = mp.rbegin(); it != mp.rend(); it++) {
        for (auto i: it->second) dp[i] = max(rmax[r[i]], cmax[c[i]]);
        for (auto i: it->second) {
            rmax[r[i]] = max(rmax[r[i]], dp[i] + 1);
            cmax[c[i]] = max(cmax[c[i]], dp[i] + 1);
        }
    }
    for (int i = 1; i <= n; i++) printf("%d\n", dp[i]);
    return 0;
}

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].

评测记录

ABC 321

E. Complete Binary Tree (完全二叉树性质、模拟)

题意

多组询问,每组询问,给一个完全二叉树,节点数为 \(N\),对于节点 \(X\),求有多少个节点距离 \(X\) 的距离为 \(K\)

数据范围
\(1\leq T\leq 10^5\)
\(1\leq N\leq 10^{18}\)
\(1\leq X \leq N\)
\(0\leq K \leq N-1\)

思路

  • 容易想到节点的种类有两种,一种在 X 子树内,一种在祖先节点再往下走一定距离。
  • 子节点很好求,枚举向上移动的距离(即枚举父节点的深度),结合完全二叉树的性质进行模拟。

评测记录

posted @ 2022-11-21 20:27  Roshin  阅读(356)  评论(0编辑  收藏  举报
-->