AtCoder Beginner Contest 328
B - 11/11
难度: ⭐
题目大意
在某个世界一年有n个月, 每个月有di天, 问有多少个日期, 该日期和月份组成的数字都是一样的; eg: 11月的1日, 22月的22日;
解题思路
暴力就行;
神秘代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+10;
int n,m;
int f[N];
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
int x,y;
cin>>x;
if(i<10) y=i;
else{
if(i%10==i/10) y=i%10;
else continue;
}
if(x>=y*10+y) m+=2;
else if(x>=y) m++;
}
cout<<m;
return 0;
}
C - Consecutive
难度: ⭐⭐
题目大意
给定一个字符串和m个询问, 每个询问都是一个区间, 问区间中有多少个连续两个相同字母的情况, "sss"看作2个;
解题思路
用前缀和来做就行, 注意边界问题;
神秘代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=3e5+10;
int n,m;
int f[N];
signed main(){
cin>>n>>m;
string s;
cin>>s;
s=' '+s;
for(int i=1;i<=n;i++){
if(s[i]==s[i+1]){
f[i]=f[i-1]+1;
}
else f[i]=f[i-1];
}
while(m--){
int l,r;
cin>>l>>r;
cout<<f[r-1]-f[l-1]<<endl;
}
return 0;
}
D - Take ABC
难度: ⭐⭐⭐
题目大意
给定一个字符串, 从前往后遍历, 如果遇到连续的"ABC"就可以清除这三个字母, 注意在清除后新生成的也算; 输出操作后的字符串;
解题思路
跟括号匹配一个思想, 我们用一个堆才存放字母, 如果当前字母是'C', 并且堆中前两个是'A'和'B', 那么就可以把他们删掉; 所以只需要判定'C'即可;
神秘代码
#include<bits/stdc++.h>
//#define int long long
#define IOS ios::sync_with_stdio(false), cin.tie(0),cout.tie(0)
#define endl '\n'
using namespace std;
const int N = 5e5 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m, idx;
char f[N];
signed main(){
string s;
cin>>s;
s=' '+s;
for(int i=1;i<s.size();i++){
if(s[i]=='C'){
if(idx>=2&&f[idx-1]=='A'&&f[idx]=='B') idx-=2;
else f[++idx]='C';
}
else f[++idx]=s[i];
}
for(int i=1;i<=idx;i++) cout<<f[i];
return 0;
}
E - Modulo MST
难度: ⭐⭐⭐⭐
题目大意
给定n个点和m条边以及边的权值, 问这个图取模后的最小生成树的边权和是多少;
解题思路
因为本题是问取模后的最小值, 所以不能用之前学过的算法; 因为数据范围很小, 所以可以考虑暴力枚举, 暴力枚举时我们可以借鉴prim的思想, 我们把结点1作为生成树的根节点, 然后我们dfs节点2~n, 对于每个节点, 我们从所有与它相连的节点中挑选一个作为它的父节点; dfs完第n个节点后我们就得检查一下当前得到的生成树是否合法, 需要检查两个点, 一是每个节点都可以到达根节点, 二是没有环; 第一点可以根据dfs中得到的父子关系进行寻找, 第二点只需要在寻找的时候打个标记即可;
在ac后我突然想到以上的过程可以用并查集来优化, 我们只需要把kruskal算法暴力一下, 我们还是从节点1开始dfs, 然后通过并查集把所有节点连到一起, 这样到最后就不需要检查了, 因为并查集会自动避免非法情况, 但是使用并查集会有一个隐藏的坑, 就是关于dfs的回溯问题, 因为大部分并查集的板子会修改在find函数中修改中间节点的父节点来压缩路径, 但是这样回溯时还需要一一修改, 所以我们可以修改为不压缩路径即可;
而且中间还有一个插曲, 我发现在dfs中p[find(u)] = find(i)改成p[u] = find(i)也可以ac, 在模拟后发现我的这种写法是从下往上去建立并查集的链, 所以每次dfs中u都是某条链中最靠上的节点(也就是这条链的祖宗节点), 所以这两种写法是一致的;
神秘代码
//朴素
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false), cin.tie(0),cout.tie(0)
#define endl '\n'
using namespace std;
const int N = 100 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m, idx, k;
int res = 1e17;
int p[N];
bool st[N][N];
int f[N][N];
bool check(){
for(int i=2;i<=n;i++){
int x=i;
map<int,int> mp;
mp[i]=1;
while(1){
if(!p[x]){
if(x!=1) return false;
break;
}
x=p[x];
if(mp[x]) return false;
mp[x]=1;
}
}
return true;
}
void dfs(int u,int sum){
if(u>n){
if(check())res=min(res,sum);
return;
}
for(int i=1;i<=n;i++){
if(st[u][i]){
p[u]=i;
dfs(u+1,(sum+f[u][i])%k);
}
}
}
signed main() {
cin >> n >> m >> k;
for (int i = 1; i <= m; i++) {
int a, b, c;
cin >> a >> b >> c;
f[a][b] = f[b][a] = c;
st[a][b] = st[b][a] = true;
}
dfs(2,0);
cout<<res;
return 0;
}
//并查集
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false), cin.tie(0),cout.tie(0)
#define endl '\n'
using namespace std;
const int N = 100 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m, idx, k;
int res = 1e17;
int p[N];
bool st[110][110];
int f[110][110];
int find(int u){
if(p[u] != u) return find(p[u]);
else return p[u];
}
void dfs(int u,int sum){
if(u==n){
res=min(res,sum%k);
return;
}
for(int i=1;i<=n;i++){
int a=find(u), b=find(i);
if(st[u][i]&&a!=b){
p[a]=b;
dfs(u+1,sum+f[u][i]);
p[a]=a;
}
}
}
signed main() {
cin >> n >> m >> k;
for(int i=1;i<=n;i++) p[i]=i;
for (int i = 1; i <= m; i++) {
int a, b, c;
cin >> a >> b >> c;
f[a][b] = f[b][a] = c;
st[a][b] = st[b][a] = true;
}
dfs(1,0);
cout<<res;
return 0;
}
F - Good Set Query
难度: ⭐⭐⭐(⭐)
题目大意
给定 Q 个三元组 (a1, b1,d1), (a2, b2, d2),...,(aQ, bQ, dQ);
集合 {1,2,...,Q} 的一个子集 S 被定义为好的子集,当且仅当存在一个长度为 N 的序列 (X1, X2,...,XN) 满足以下条件:对于所有i, Xai - Xbi = di ;
将 S 初始化为空集,对于 i=1,2,...,Q 依次进行以下操作:
如果 S∪{i} 是一个好的集合,那么将 S 替换成 S∪{i} 。以升序输出 S 中的所有元素。
解题思路
一道带权并查集的板子题; 本题不能去考虑每个X具体是多少, 我们只需要考虑每个位置直接的差值是多少即可, 对于每个三元组(ai, bi,di), 我们可以在bi和ai直接连一条边, 边的权值为di; 可以用带权并查集来维护这个图, 如果ai和bi不在同一个连通块, 说明Xai和Xbi之间的差值还没有定, 我们就可以直接把他们连起来, 差值定为di就好了; 我们真正需要看的是已经在同一个连通块中的, 也就是Xai和Xbi之间的差值在前面已经定好了, 我们只需要检查当前这个三元组是否满足这个差值;
维护这个差值就需要带权并查集, 相比于普通并查集, 带权并查集多了一个属性dis, 指的是该节点到祖宗节点之间的距离; 如果节点ai和bi之间的距离为di, 那么根据ai和bi的dis, 我们就可以知道ai的祖宗节点x和bi的祖宗节点y之间的距离, 因为我们合并连通块的时候是更改祖宗节点的父节点, 所以我们只需要改变祖宗节点的dis就可以了; 祖宗节点的dis原本为0, 合并后为dis[bi] - dis[ai] + di;(画个图就很好理解, 类似于向量的加减);
神秘代码
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false), cin.tie(0),cout.tie(0)
#define endl '\n'
using namespace std;
const int N = 2e5 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m, res;
int p[N], dis[N];
set<int> s;
int find(int x){
if(p[x] != x){
int t = p[x];
p[x] = find(p[x]);
dis[x] += dis[t];
}
return p[x];
}
signed main(){
cin >> n >> m;
for(int i = 1; i <= n; i++) {
p[i] = i;
dis[i] = 0;
}
for(int i = 1; i <= m; i++){
int a, b, d;
cin >> a >> b >> d;
int x = find(a), y = find(b);
if(x != y){
p[x] = y;
dis[x] = dis[b] - dis[a] + d;
s.insert(i);
}
else if(dis[a] == dis[b] + d) s.insert(i);
}
for(int x: s) cout << x <<' ';
return 0;
}