ABC-D题集锦
ABC139D. ModSum
题意:对于 \(1\sim n\) 的排列 \(P\),求 \(\sum i\%P_i\) 的最大值。
容易证明,最优排列为 \(\{2,3,4,\cdots,n,1\}\),答案为 \(1+2+\cdots+(n-1)=\frac{n(n-1)}{2}\)。
By risujiroh
#include <bits/stdc++.h>
using namespace std;
using lint = long long;
template<class T = int> using V = vector<T>;
template<class T = int> using VV = V< V<T> >;
int main() {
cin.tie(nullptr); ios::sync_with_stdio(false);
lint n; cin >> n;
cout << n * (n - 1) / 2 << '\n';
}
ABC140D. Face Produces Unhappiness
题意:有 \(n\) 个人排队,每个人要么面向左要么面向右。如果一个人前面有人并且和他面向的方向相同,则称这个人是开心的。你可以进行不超过 \(k\) 次操作,每次操作可以将一段区间内的人翻转(序列反转、方向反转),问最多的开心人数。
思考可得不开心人数等于连续的段数,每次操作可以将中间的一段翻转,此时连续段数 \(-2\)。由于至少有一段,所以答案为 \(\max(cnt-2k,1)\)。
By jah_melon
#include<bits/stdc++.h>
#define ll long long
using namespace std;
template <typename T> void read(T &x){
x=0;char ch=getchar();int fh=1;
while (ch<'0'||ch>'9'){if (ch=='-')fh=-1;ch=getchar();}
while (ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
x*=fh;
}
int m,n,sum=1;
string s;
int main(){
cin>>n>>m;
cin>>s;
for (int i=0;i<s.length()-1;i++)if (s[i]!=s[i+1])sum++;
sum=max(1,sum-m*2);
cout<<n-sum<<endl;
return 0;
}
ABC141D. Powerful Discount Tickets
题意:有 \(n\) 件商品和 \(m\) 张优惠券,每对一个商品使用一张优惠券,该商品的价格都会减半(下取整)。问最小总价。
显然每次取最贵的商品使用优惠券。用优先队列维护即可。
By heno239
void solve() {
priority_queue<int> q;
int n, m; cin >> n >> m;
rep(i, n) {
int a; cin >> a; q.push(a);
}
rep(i, m) {
int x = q.top(); q.pop();
q.push(x / 2);
}
ll sum = 0;
while (!q.empty()) {
sum += q.top(); q.pop();
}
cout << sum << endl;
}
signed main() {
cin.tie(0);
ios::sync_with_stdio(false);
solve();
//stop
return 0;
}
ABC142D. Disjoint Set of Common Divisors
题意:给你两个数 \(a,b\),求最多能取多少数满足:这些数两两互质;这些数是 \(a,b\) 的公因数。
\(a,b\) 的公因数一定是 \(\gcd(a,b)\) 的因数,而要从这些因数中取尽可能多互质的数,则一定是取 \(\gcd(a,b)\) 的全部质因数(和 \(1\))。
By risujiroh
template<class Z> map<Z, int> factorize(Z n) {
map<Z, int> res;
for (Z i = 2; i * i <= n; ++i) while (n % i == 0) ++res[i], n /= i;
if (n != 1) ++res[n];
return res;
}
int main() {
cin.tie(nullptr); ios::sync_with_stdio(false);
lint a, b; cin >> a >> b;
auto mp = factorize(__gcd(a, b));
cout << mp.size() + 1 << '\n';
}
ABC143D. Triangles
题意:有 \(n\) 根互相区分的木棍,第 \(i\) 根长度为 \(a_i\),求能组成多少个本质不同的非退化三角形。
先从小到大排序,枚举 \((i,j)\),找以 \(i,j\) 为较短边的三角形数量,即找 \(k>j\) 且 \(a_k<a_i+a_j\) 的数量。二分查找即可。
By sumitacchan
signed main(){
int N; cin >> N;
vec L(N); cin >> L;
Sort(L);
int ans = 0;
REP(i, N) FOR(j, i + 1, N){
ans += Lower_bound(L, L[i] + L[j]) - j - 1;
}
Out(ans);
return 0;
}
ABC144D. Water Bottle
题意:一个底为 \(a\times a\)、高为 \(b\) 的长方体容器,装着 \(x\) 体积的水(\(x\le a^2b\))。问最多能倾斜多少度使得水不倒出来。
将 \(x\) 除以一个 \(a\),转化成二维问题。然后画一下图得到两种情况的式子,调用反三角函数计算后转成角度即可。
By jiangly
#include <bits/stdc++.h>
using namespace std;
constexpr double PI = atan2(0, -1);
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int a, b, x;
cin >> a >> b >> x;
double y = 1.0 * x / a;
double g;
if (2 * y <= a * b)
g = atan2(b, 2 * y / b);
else
g = atan2(2 * (a * b - y) / a, a);
cout << setprecision(10) << fixed << g / PI * 180 << endl;
return 0;
}
ABC145D. Knight
题意:初始在 \((0,0)\),每次可以移动 \((1,2)\) 或 \((2,1)\),求走到 \((X,Y)\) 的方案数。
解方程组 \(\begin{cases}2p+q=Y\\p+2q=Y\end{cases}\),答案即为 \(\begin{pmatrix}p+q\\q\end{pmatrix}\)。
const int N = 2e6;
vector<int> facs(N);
vector<int> invfacs(N);
void init()
{
facs[0] = 1;
for (int i = 1; i<N; i++) facs[i] = mul(facs[i-1], i);
invfacs[N-1] = inv(facs[N-1]);
for (int i = N-2; i>=0; i--) invfacs[i] = mul(invfacs[i+1], i+1);
}
int main() {
ios_base::sync_with_stdio(0);
cin.tie(nullptr);
init();
int x, y;
cin>>x>>y;
if ((x+y)%3) {cout<<0; return 0;}
int n = (x+y)/3;
x-=n;
y-=n;
if (x<0||y<0) {cout<<0; return 0;}
cout<<mul(facs[n], mul(invfacs[x], invfacs[y]));
}
ABC146D. Coloring Edges on Tree
题意:一棵 \(n\) 个节点的树,你需要给每条边上色,要求对于每个顶点,连的边的颜色互不相同。求最小的颜色数量并输出方案。
做法 1
贪心,DFS 时传入与父节点连的边的颜色,与儿子节点之间涂色时从小到大涂色,并刻意避开该颜色即可。
By sky58
vector<pint> gr[100100];
int col=0;
int out[100100];
void dfs(int v,int u,int c){
int ne=1;
rep(i,gr[v].size()){
pint p=gr[v][i];
if(p.fi==u) continue;
if(ne==c) ne++;
out[p.se]=ne;
dfs(p.fi,v,ne);
ne++;
}
}
int main()
{
int n,a,b;
cin>>n;
rep(i,n-1){
cin>>a>>b;gr[a].pb(mp(b,i));gr[b].pb(mp(a,i));
}
REP(i,1,n+1) col=max(col,(int)gr[i].size());
dfs(1,0,0);
cout<<col<<endl;
rep(i,n-1) cout<<out[i]<<endl;
}
做法 2
注意到使用的颜色个数一定为最大的节点度数,而在此前提下不一定每个点的边颜色最小,只要互不相同即可。又可以发现,连续 \(\le k\) 个正整数 \(\% k\) 的结果一定互不相同,于是每个节点往儿子涂色时只要从父边的颜色加一开始连续地涂即可。最终每条边的实际颜色即为对 \(k\) 取模(加一)的值。
By hitonanode
vector<set<int>> e;
map<pint, int> ma;
void dfs(int now, int prv, int cnow)
{
ma[pint(now, prv)] = ma[pint(prv, now)] = cnow++;
for (auto nxt : e[now]) if (nxt != prv)
{
dfs(nxt, now, cnow++);
}
}
int main()
{
lint N;
cin >> N;
e.resize(N);
vector<pint> p;
REP(i, N - 1)
{
int a, b;
cin >> a >> b;
a--, b--;
e[a].insert(b);
e[b].insert(a);
p.emplace_back(a, b);
}
int c = 0;
REP(i, N) mmax(c, (int)e[i].size());
dfs(0, -1, 0);
printf("%d\n", c);
for (auto pa : p) printf("%d\n", ma[pa] % c + 1);
}
ABC147D. Xor Sum 4
题意:给你一个序列 \(a\),求 \(\sum\limits_{i=1}^{n-1}\sum\limits_{j=i+1}^n a_i \text{ xor } a_j\)。
对于每一位分开处理,考虑这一位在多少项里为 \(1\)。设该位为 \(1\) 的元素个数为 \(cnt\),则异或结果为 \(1\) 的方案数为 \(cnt(n-cnt)\),造成的贡献即为方案数乘上该位的权值。
By jiangly
#include <bits/stdc++.h>
using namespace std;
constexpr int P = 1'000'000'007;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<long long> a(n);
for (int i = 0; i < n; ++i)
cin >> a[i];
int ans = 0;
for (int k = 0; k < 60; ++k) {
int c[2] = {};
for (int i = 0; i < n; ++i)
++c[a[i] >> k & 1];
ans = (ans + (1LL << k) % P * c[0] % P * c[1]) % P;
}
cout << ans << endl;
return 0;
}
ABC148D. Brick Break
题意:给你一个长度为 \(n\) 的序列 \(a\),求最大的 \(K\),使得存在一个长度为 \(K\) 的子序列,元素值依次从 \(1\) 到 \(K\)。
一位一位一次考虑即可。对于每一位,在能选的情况下显然应该贪心地选最靠前的那个。
By cjy
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,a,last;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d",&a);
if(a==last+1)++last;
}
if(!last)printf("-1");
else printf("%d",n-last);
return 0;
}
ABC159D. Banned K
题意:给你一个长度为 \(n\) 的序列,对于每个下标,求出删掉这个数后,序列中取出两个相同数的方案数。
先求出总的方案数,再对于每个 \(k\),\(O(1)\) 求出变化。
By kotatsugame
n=read()
for(i=0;i<n;i++){
x[i]=read()
ans+=c[x[i]]++
}
for(i=0;i<n;i++)ans-c[x[i]]+1
ABC160D. Line++
题意:有 \(n\) 个点,对于每个 \(i<n\),连一条 \(i\sim i+1\) 的边,最后再连一条 \(X\sim Y\) 的边。对于每个 \(1\le K<n\) 求出最短路为 \(K\) 的点对个数。
\(n^2\) 枚举点对直接计算即可。
By kotatsugame
#include<iostream>
using namespace std;
int N,X,Y;
int cnt[2020];
main()
{
cin>>N>>X>>Y;
X--,Y--;
for(int i=0;i<N;i++)
{
for(int j=i+1;j<N;j++)
{
cnt[min(j-i,abs(X-i)+1+abs(Y-j))]++;
}
}
for(int k=1;k<N;k++)cout<<cnt[k]<<endl;
}
ABC161D. Lunlun Number
题意:定义一个数为 Lunlun Number,当且仅当这个数十进制下的相邻两位相差不超过 \(1\)(不能有前导零)。求第 \(K\) 小的 Lunlun Number。
做法 1
直接暴力转移 \(K\) 次。每次转移找最低的还没跑满的位 \(+1\),后面的位全部恢复尽可能低的值;如果全部跑满了则创建新位。
By myself
#include<bits/stdc++.h>
using namespace std;
signed main() {
int k;
cin>>k;
k--;
vector<int> now;
now.push_back(1);
while(k--) {
bool flag=0;
for(int i=0;i<now.size();i++)
if((i==now.size()-1||now[i]<=now[i+1])&&now[i]!=9) {
now[i]++,flag=1;
for(int j=i-1;j>=0;j--)
now[j]=max(0,now[j+1]-1);
break;
}
if(!flag) {
now.push_back(1);
for(int i=0;i<now.size()-1;i++)
now[i]=0;
}
}
for(int i=now.size()-1;i>=0;i--)
cout<<now[i];
cout<<endl;
return 0;
}
做法 2
从 1~9 开始 BFS,每次从一个数往后搜,即十进制下左移一位,再将最后一位考虑上三种可能性(\(-1,0,+1\))。注意到此时 BFS 的队列一定是优先队列,搜到第 \(K\) 个输出即可。
By SSRS
#include <bits/stdc++.h>
using namespace std;
int main(){
int K;
cin >> K;
queue<long long> Q;
for (int i = 1; i <= 9; i++){
Q.push(i);
}
while (true){
long long x = Q.front();
Q.pop();
K--;
if (K == 0){
cout << x << endl;
break;
}
int r = x % 10;
if (r != 0){
Q.push(x * 10 + r - 1);
}
Q.push(x * 10 + r);
if (r != 9){
Q.push(x * 10 + r + 1);
}
}
}
做法 3
可以使用 DFS 搜出范围内所有的解,再将所有解排序,输出第 \(K\) 小值。
By risujiroh
#include <bits/stdc++.h>
using namespace std;
int main() {
cin.tie(nullptr);
ios::sync_with_stdio(false);
int k;
cin >> k;
--k;
vector<long long> a;
string s;
auto dfs = [&](auto&& self) -> void {
if (not s.empty()) {
a.push_back(stoll(s));
}
if (s.size() == 10) {
return;
}
if (s.empty()) {
for (char c = '1'; c <= '9'; ++c) {
s += c;
self(self);
s.pop_back();
}
} else {
for (char c = '0'; c <= '9'; ++c) {
if (abs(c - s.back()) <= 1) {
s += c;
self(self);
s.pop_back();
}
}
}
};
dfs(dfs);
sort(begin(a), end(a));
cout << a[k] << '\n';
}
此做法与 BFS 的区别在于,BFS 保证了搜索顺序从小到大,找到第 \(K\) 个可以直接输出;而 DFS 不能保证顺序,所以需要全部存下来排序。
ABC162D. RGB Triplets
题意:有一个由 RGB 组成的字符串,问有多少个三元组 \((i,j,k)\) 满足 \(k-j\ne j-i\) 且 \(s_i\ne s_j\ne s_k\)
对于每个位置考虑计算它作为 \(j\) 时的方案数。开两个桶求出左边和右边每个颜色的个数,计算答案,再减去等差情况的答案。
By kotatsugame
#include<iostream>
using namespace std;
string s;
long ans;
int N;
int id(char c){return c=='R'?0:c=='G'?1:2;}
main()
{
cin>>N>>s;
for(int i=0;i<N;i++)
{
long L[3]={},R[3]={};
for(int j=i-1;j>=0;j--)L[id(s[j])]++;
for(int j=i+1;j<N;j++)R[id(s[j])]++;
for(int j=0;j<3;j++)for(int k=0;k<3;k++)
{
if(id(s[i])!=j&&id(s[i])!=k&&j!=k)ans+=L[j]*R[k];
}
for(int k=1;;k++)
{
if(i-k<0||i+k>=N)break;
if(s[i-k]!=s[i]&&s[i]!=s[i+k]&&s[i-k]!=s[i+k])ans--;
}
}
cout<<ans<<endl;
}
ABC235D. Multiply and Rotate
题意:每次操作可以将一个数 \(\times a\) 或将最后一位挪到最前面。问将 \(1\) 变成 \(n\) 的最小次数。
BFS 搜索即可。
By heno239
void solve() {
ll a, n; cin >> a >> n;
ll sz = 1;
while (sz <= n)sz *= 10;
vector<int> dist(sz, mod);
queue<int> q;
q.push(1);
dist[1] = 0;
while (!q.empty()) {
ll x = q.front(); q.pop();
int nd = dist[x] + 1;
if (x * a < sz) {
if (nd < dist[x * a]) {
dist[x * a] = nd;
q.push(x * a);
}
}
if (x >= 10 && x % 10) {
string s = to_string(x);
s.insert(s.begin(), s.back());
s.pop_back();
int nx = stoi(s);
if (nd < dist[nx]) {
dist[nx] = nd;
q.push(nx);
}
}
}
int ans = dist[n];
if (ans == mod)ans = -1;
cout << ans << "\n";
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout << fixed << setprecision(10);
//init_f();
//init();
//while(true)
//useexpr();
//int t; cin >> t; rep(i, t)
solve();
return 0;
}
ABC236D. Dance
题意:有 \(2n\) 个人两两分组,给定每两个人分到一组的贡献,求最大总贡献。
暴搜即可。每次搜的时候考虑第一个没选的人选谁。
By tourist
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
vector<vector<int>> a(2 * n, vector<int>(2 * n));
for (int i = 0; i < 2 * n; i++) {
for (int j = i + 1; j < 2 * n; j++) {
cin >> a[i][j];
}
}
vector<bool> used(2 * n, false);
int ans = 0;
function<void(int)> Dfs = [&](int w) {
int i = 0;
while (i < 2 * n && used[i]) {
++i;
}
if (i == 2 * n) {
ans = max(ans, w);
return;
}
for (int j = i + 1; j < 2 * n; j++) {
if (!used[j]) {
used[i] = used[j] = true;
Dfs(w ^ a[i][j]);
used[i] = used[j] = false;
}
}
};
Dfs(0);
cout << ans << '\n';
return 0;
}
ABC268D. Unique Username
题意:你需要用 \(s_1\sim s_n\) 这 \(n\) 个字符串构造一个名字。具体地,将这 \(n\) 个字符串重新排列,相邻两个之间加入至少一个下划线。要求这个名字不能和给定的若干个限制名字重复,且长度必须在 \([3,16]\) 之间。输出任意满足条件一个名字即可。
直接 DFS,每次新加入一个 \(s\) 中元素。回溯时清楚标记。
By heno239
void solve() {
int n; cin >> n;
int m; cin >> m;
vector<string> s(n);
rep(i, n)cin >> s[i];
set<string> st;
rep(i, m) {
string t; cin >> t; st.insert(t);
}
string ans="-1";
string cur;
vector<bool> used(n);
function<bool()> dfs = [&]() {
if (cur.size() > 16)return false;
bool valid = false;
if (cur.size() >= 3) {
valid = true;
rep(i, n)if (!used[i])valid = false;
if (st.count(cur))valid = false;
if (cur.back() == '_')valid = false;
}
if (valid) {
ans = cur; return true;
}
if (cur.size()) {
cur.push_back('_');
if (dfs())return true;
cur.pop_back();
}
if (cur.empty()||cur.back()== '_') {
rep(i, n)if (!used[i]) {
string cop = cur;
cur += s[i];
used[i] = true;
if (dfs())return true;
cur = cop;
used[i] = false;
}
}
return false;
};
dfs();
cout << ans << "\n";
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
//cout << fixed << setprecision(10);
init_f();
//init();
//expr();
//while(true)
//int t; cin >> t; rep(i, t)
solve();
return 0;
}
ABC269D. Do use hexagon grid
题意:对于一个坐标,称它上、下、左、右、左下、右下方的坐标与它相临。给你若干个坐标,判断有几个连通块。
\(n^2\) 连边后搜索数连通块个数即可。
By SSRS
#include <bits/stdc++.h>
using namespace std;
vector<int> dx = {-1, -1, 0, 0, 1, 1};
vector<int> dy = {-1, 0, -1, 1, 0, 1};
int main(){
int N;
cin >> N;
vector<int> X(N), Y(N);
for (int i = 0; i < N; i++){
cin >> X[i] >> Y[i];
}
map<pair<int, int>, int> mp;
for (int i = 0; i < N; i++){
mp[make_pair(X[i], Y[i])] = i;
}
vector<bool> used(N, false);
int ans = 0;
for (int i = 0; i < N; i++){
if (!used[i]){
used[i] = true;
queue<int> Q;
Q.push(i);
while (!Q.empty()){
int v = Q.front();
Q.pop();
for (int j = 0; j < 6; j++){
int x = X[v] + dx[j];
int y = Y[v] + dy[j];
if (mp.count(make_pair(x, y)) == 1){
int w = mp[make_pair(x, y)];
if (!used[w]){
used[w] = true;
Q.push(w);
}
}
}
}
ans++;
}
}
cout << ans << endl;
}
ABC220D. FG operation
题意:有一个栈,栈中元素为 \(0\sim 9\),每次操作可以取出栈顶的两个元素,再将它们的和或者它们的积 \(\pmod{10}\) 压入栈顶。对于每个 \(i\in [0,9]\),求出最终剩下 \(i\) 的方案数。
由于每次操作不确定的值仅为栈顶元素,考虑 \(f[i][j\in[0,9]]\) 表示进行 \(i\) 次操作后栈顶元素为 \(j\) 的方案数,则
使用刷表法可以将转移优化成 \(O(1)\),即:
By jiangly
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int N;
std::cin >> N;
Z f[10];
int A0;
std::cin >> A0;
f[A0] = 1;
for (int i = 1; i < N; i++) {
int x;
std::cin >> x;
Z g[10];
for (int y = 0; y < 10; y++) {
g[x * y % 10] += f[y];
g[(x + y) % 10] += f[y];
}
std::copy(g, g + 10, f);
}
for (int i = 0; i < 10; i++) {
std::cout << f[i].val() << "\n";
}
return 0;
}
ABC232D. Weak Takahashi
题意:有一个 \(n\times m\) 的迷宫,
#
为墙、.
为空地。从左上角开始,每次只能往右或往下走,求最长路经。
做法 1
设 \(f[i][j]\) 表示走到 \((i,j)\) 的最长路长度,如果 \((i,j)\) 为墙则为 \(-\mathrm{inf}\),则 \(f[i][j]=\max(f[i-1][j],f[i][j-1])+1\)。
By natsugiri
int H, W;
char C[111][111];
int D[111][111];
void MAIN() {
scanf("%d%d", &H, &W);
REP (i, H) scanf("%s", C[i]);
memset(D, 0xc0, sizeof D);
D[0][0] = 1;
int ans = 1;
REP (i, H) REP (j, W) if (C[i][j] == '.') {
if (i) amax(D[i][j], D[i-1][j]+1);
if (j) amax(D[i][j], D[i][j-1]+1);
amax(ans, D[i][j]);
}
printf("%d\n", ans);
}
int main() {
int TC = 1;
// scanf("%d", &TC);
REP (tc, TC) MAIN();
return 0;
}
做法 2
令 \(f[i][j]\) 表示从 \((i,j)\) 开始走的最长路,则 \(f[i][j]=\begin{cases}0,\ \text{ if }(i,j) \text{ is a space}\\max(f[i+1][j],f[i][j+1])+1, \text{otherwise}\end{cases}\)
这样可以使有墙的情况的处理更加简洁。
By tourist
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int h, w;
cin >> h >> w;
vector<string> g(h);
for (int i = 0; i < h; i++) {
cin >> g[i];
}
vector<vector<int>> f(h, vector<int>(w));
for (int i = h - 1; i >= 0; i--) {
for (int j = w - 1; j >= 0; j--) {
if (g[i][j] == '#') {
f[i][j] = 0;
} else {
f[i][j] = 1;
if (i + 1 < h && g[i + 1][j] == '.') {
f[i][j] = max(f[i][j], f[i + 1][j] + 1);
}
if (j + 1 < w && g[i][j + 1] == '.') {
f[i][j] = max(f[i][j], f[i][j + 1] + 1);
}
}
}
}
cout << f[0][0] << '\n';
return 0;
}