ACM日常训练日记——7.30(并查集)
- nowcoder训练
(并查集专项)- DongDong认亲戚
这道题关键在于怎么去把字符串存入并查集,我们只需要开一个unordered_map存每一个字符串的下标即可,再上模板
- DongDong认亲戚
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5005;
unordered_map<string,int>mp;
int n,m;
int Fa[maxn],Rank[maxn];
void init(int n) ///初始化
{
for (int i = 1; i <= n; i++)
{
Fa[i] = i;
Rank[i] = 1;
}
}
int find(int x) //查找
{
return x == Fa[x]? x:(Fa[x] = find(Fa[x]));//路径压缩
}
void merge(int i, int j) //合并
{
int x = find(i), y = find(j);
if (Rank[x] <= Rank[y])
{
Fa[x] = y;
}
else
{
Fa[y] = x;
}
if (Rank[x] == Rank[y] && x!=y)
{
Rank[y]++;
}
}
int main(){
cin>>n>>m;
init(n);
string a;
for(int i=1;i<=n;i++)cin>>a,mp[a]=i;
for(int i=1;i<=m;i++){
int a;
string b,c;
cin>>a>>b>>c;
if(a==1){
merge(mp[c],mp[b]);
}else
{
if(find(mp[b])==find(mp[c])){
cout<<1<<'\n';
}else
cout<<0<<'\n';
}
}
}
- DongDong认亲戚
没学过怎么集合里面的元素全部判断是否同一个并查集里面,这道题学到了,代码给的很详细了
#include <bits/stdc++.h>
using namespace std;
//x[i][j]代表第i组中玩j号游戏的人数,
map<int,int>x[100001];
//记录第i组的父亲是谁
int fa[100001];
//cnt[i]代表第i组下有cnt[i]个人,按秩合并用的
int cnt[100001];
//num[i]代表总共有num[i]个人玩第i号游戏
int num[100001];
//答案要按照游戏的序号输出,所以我们存一下
int ans[100001];
void init(int n){
for(int i=1;i<=n;i++){
x[i].clear();
fa[i]=i;
cnt[i]=1;
ans[i]=0;
num[i]=0;
}
}
int find(int a){
if(fa[a]==a){
return a;
}
return fa[a]=find(fa[a]);
}
int main(){
int n,k,m;
//题目说有多组输入,我在这卡了半小时,无语了家人们
while(cin>>n>>k>>m){
//多组样例初始化肯定要的
init(n);
for(int i=1;i<=n;i++){
int t;
cin>>t;
num[t]++;
x[i][t]++;
}
for(int i=0;i<m;i++){
int a,b;
cin>>a>>b;
int A=find(a);
int B=find(b);
if(A==B){
continue;
}
//这里如果不按秩合并,树会退化成链表,造成mle
if(cnt[A]>cnt[B]){
swap(A,B);
swap(a,b);
}
if(A!=B){
fa[A]=B;
cnt[B]+=cnt[A];
cnt[A]=0;
//把A中的每个游戏的人数对应地加到B中
for(auto it : x[A]){
x[B][it.first]+=it.second;
if(x[B][it.first]==num[it.first]){
ans[it.first]=i+1;
}
}
}
}
for(int i=1;i<=k;i++){
//如果这个游戏只有一个人玩,直接开始
if(num[i]==1){
cout<<0<<endl;
}else if(ans[i]==0){//到死也玩不上,呜呜呜
cout<<-1<<endl;
}else{
cout<<ans[i]<<endl;
}
}
}
return 0;
}
- 加边的无向图
简单的并查集,查询并查集的个数,个数减一就是答案,这道题把模板都用到了
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5000005;
int n,m,q;
int Fa[maxn],Rank[maxn];
set<int>st;
void init(int n) ///初始化
{
for (int i = 1; i <= n; i++)
{
Fa[i] = i;
Rank[i] = 1;
}
}
int find(int x) //查找
{
return x == Fa[x]? x:(Fa[x] = find(Fa[x]));//路径压缩
}
void merge(int i, int j) //合并
{
int x = find(i), y = find(j);
if (Rank[x] <= Rank[y])
{
Fa[x] = y;
}
else
{
Fa[y] = x;
}
if (Rank[x] == Rank[y] && x!=y)
{
Rank[y]++;
}
}
int cut(int n)
{
int cnt = 0;
for (int i = 1; i <= n; i++)
if (find(i) == i)
cnt++;
return cnt;
}
int main(){
cin>>n>>m;
init(n);
int a,b;
init(n);
for(int i=1;i<=m;i++){
cin>>a>>b;
merge(a,b);
}
cout<<cut(n)-1;
}
-
codeforces
- A. Strong Password
计算插入插入的字符使得时间最小,暴力即可
- A. Strong Password
#include <bits/stdc++.h>
using namespace std;
int calculateTime(const string& s) {
int time = 2;
for (int i = 1; i < s.length(); i++) {
time += (s[i] == s[i-1]) ? 1 : 2;
}
return time;
}
string insertBestChar(const string& s) {
string best = s;
int maxTime = 0;
for (int i = 0; i <= s.length(); i++) {
for (char c = 'a'; c <= 'z'; c++) {
string newStr = s.substr(0, i) + c + s.substr(i);
int time = calculateTime(newStr);
if (time > maxTime) {
maxTime = time;
best = newStr;
}
}
}
return best;
}
int main() {
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int t;
cin >> t;
while (t--) {
string s;
cin >> s;
cout << insertBestChar(s) <<'\n';
}
return 0;
}
- B. Make Three Regions
题读假了,最开始只有一个连通块,以为可能不同,服了,就两行判断两个x中间有没有点以及下面点的情况wa3发才过
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int solve(const vector<string>& grid) {
int n = grid[0].size();
int count = 0;
for (int j = 1; j < n - 1; ++j) {
// 检查第一行
if (grid[0][j] == '.' && grid[1][j] == '.' && grid[1][j+1]=='.'&&grid[1][j-1]=='.'
&&grid[0][j-1] == 'x' && grid[0][j+1] == 'x') {
count++;
}
// 检查第二行
if (grid[1][j] == '.' && grid[0][j] == '.' && grid[0][j+1]=='.'&&grid[0][j-1]=='.'
&& grid[1][j-1] == 'x' && grid[1][j+1] == 'x') {
count++;
}
}
return count;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<string> grid(2);
cin >> grid[0] >> grid[1];
cout << solve(grid) << '\n';
}
return 0;
}
- C. Even Positions
跟a题难度差不多,但是我不知道我为什么一直想用区间dp去做,而且途中换了一种写法已经接近正确答案了,但是又改回去用区间dp
#include <iostream>
#include <string>
#include <stack>
#include <vector>
using namespace std;
long long solve(const string& s) {
long long ans=0;
int n = s.length();
for(int i=1;i<n;i+=2){
if(s[i]=='(')ans+=3;
else ans++;
}
return ans;
}
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
string s;
cin >> s;
cout << solve(s) << '\n';
}
return 0;
}
- 动态规划训练
区间dp
石子合并
本题针对于区间进行操作,是一个区间dp的问题。对于某个区间i,j能够得到的最大值等于在区间里面分成两部分中所有两部分的最大值。那么再看分出来的这两部分其实又可以划分两部分。
那么动态规划的状态转移方程就出来了:dp[i][j] = max(dp[i][j], dp[i][k]+dp[k+1][j]+sum[i][j]);。
最后以最短的区间来世初步动态规划边长就行。
至于圈这个特殊性,可以使用加倍的方式来代替,也就是在数列后面接上一个重复的数列,这样转一圈之后回来就可以表示出来了。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 400+10;
int n;
//dp[i][j] = max(dp[i][j], dp[i][k]+dp[k+1][j]+sum[i][j]);
int dp[maxn][maxn];
int dp_min[maxn][maxn];
int sum[maxn];
int a[maxn];
int main() {
cin>>n;
memset(dp_min, 0x3f3f3f, sizeof(dp_min));
for (int i=1;i<=n;i++) {
cin>>a[i];
// dp[i][i] = a[i];
a[i+n] = a[i];
dp_min[i][i] = 0;
dp_min[i+n][i+n] = 0;
}
for (int i=1;i<=n*2;i++) {
sum[i] = sum[i-1]+a[i];
}
int ans_max=INT_MIN;
int ans_min=INT_MAX;
for (int i=2;i<=n;i++) {
for (int l=1;l+i-1<=n*2;l++) {
int r = l+i-1;
for (int k=l;k<=r-1;k++) {
dp[l][r] = max(dp[l][r], dp[l][k]+dp[k+1][r]+sum[r]-sum[l-1]);
dp_min[l][r] = min(dp_min[l][r], dp_min[l][k]+dp_min[k+1][r]+sum[r]-sum[l-1]);
}
}
}
for (int i=1;i+n-1<=n*2;i++) {
int r = i+n-1;
ans_max = max(ans_max, dp[i][r]);
ans_min = min(ans_min, dp_min[i][r]);
}
cout<<ans_min<<endl;
cout<<ans_max<<endl;
return 0;
}