暑假训练第三周周报

本周主要学习了搜索算法,bfs,dfs,还学到了如何遍历容器,最小生成树,进制哈希算法,遇到了之前学过的快速幂算法,下周开始学习动态规划、多做题,将所学过的能灵活运用出来。

题目:小C的周末(auto遍历容器

小C的周末 (nowcoder.com)

解析:

本道题题意是说每次接一根线,看加到哪根线一个游戏就可以开始玩了,如果只有一个人玩该游戏就输出0,不存在就输出-1,否则就输出接到第几根线该种类游戏就可以开始了。这道题用并查集将每一个点连接起来,同时每次连接结束就都需要检查,是否有游戏可以开始玩了,我们拿点集(初始都是自己要玩的游戏)来记录每种游戏连接了的人数,每次添加一条网线时,就将小点集放入大点集中(这样可以减少转移耗费的时间)(同时判断该删除的点集中是否有可以开始的游戏),删除小点集。结束所有添加线后,最后按游戏编号顺序输出。

遍历小点集时,使用for(auto j : ma[v] ) 只会将容器中有的遍历一遍,不会从1~m游戏全部遍历一遍,大大减少了时间的损耗。(注意一下这样的写法,之前没有写过)。

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
int n,m,q;
int f[100005];
int ans[100005];        
map<int,int>ma[100005];
int cnt[100005];
int find(int x){
    if(f[x]==x) return x;
    return f[x]=find(f[x]);
}
signed main()
{
    ios::sync_with_stdio(0),cin.tie(0);
    while(cin>>n>>m>>q){
        for(int i=1;i<=n;i++){
            ma[i].clear();f[i]=i;
            ans[i]=-1;//答案记录数组初始值为-1
        }
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;i++){
            int x;
            cin>>x;
            cnt[x]++;
            ma[i][x]++;//自己以自己作为集群
        }
        int u,v;
        for(int k=0;k<q;k++){
            cin>>u>>v;
            u=find(u),v=find(v);
            if(ma[u].size()<ma[v].size()) swap(u,v);
            
            f[v]=u;
 			for(auto j:ma[v] ){
				ma[u][j.first]+=j.second;//当前游戏种类j.first的人数相加,mp[x][j.first]+mp[v][j.first] 
				//ans答案的统计
				if(ma[u][j.first]==cnt[j.first]&&ans[j.first]==-1)
				ans[j.first]=k+1;//当前添加网线的编号 
			} 
            ma[v].clear();//小点集删除
        }
        for(int i=1;i<=m;i++){
            if(cnt[i]==1){
                cout<<"0"<<endl;
            }
            else if(ans[i]!=0){
                cout<<ans[i]<<endl;
            }
            else{
                cout<<"-1"<<endl;
            }
        }
    }
    return 0;
}

题目:寻找道路

寻找道路 (nowcoder.com)

解析:

本道题题意是找到从起点到终点最短的路径,同时还要满足经过的所有的点的出边都要直接或间接与终点连通,找最短路径用bfs遍历即可,但是每个点的出边指向的点就需要特殊处理,需要使用·两次bfs,首先反向建图将从终点开始能达到的点标记,然后再遍历每个点所连接的点,只要他连接的点到达不了终点,也标记一下,最后遍历没有标记过的点找到最短路径。

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int, int>
int n,m;
int X,Y;
int ans[10004];
bool vis[10004];
bool vi[10004];
queue<int> q;

signed main(){
   ios::sync_with_stdio(0),cin.tie(0);
   cin>>n>>m;
   vector<int> vf[n+1];
   for(int i=0;i<m;i++){
    int x,y;
    cin>>x>>y;
    vf[y].push_back(x);
   }
   cin>>X>>Y;
    q.push(Y);
    vis[Y]=1;
    while(!q.empty()){
        int c=q.front();
        q.pop();
        for(auto i : vf[c]){
            
            if(vis[i]) continue;
            vis[i]=1;
            q.push(i);
        }
    }
    memcpy(vi,vis,sizeof(vis));
    for(int i=1;i<=n;i++){
        if(!vi[i]){//此点没有指向终点,指向它的点也不能选;
            for(auto j : vf[i]){
                vis[j]=0;
            }
        }
    }
    memset(ans,-1,sizeof(ans));
    q.push(Y);
    ans[Y]=0;
    while(!q.empty()){
        int c=q.front();
        q.pop();
        for(auto i : vf[c]){
            if(!vis[i]||ans[i]!=-1) continue;
            vis[i]=-1;
            q.push(i);
            ans[i]=ans[c]+1;
        }
    }
    cout<<ans[X]<<endl;
   return 0;
}

题目:lxy的通风报信(bfs,最小生成树)7.24

G-lxy的通风报信_河南萌新联赛2024第(二)场:南阳理工学院 (nowcoder.com)

解析:

本题算是最小生成树的模板,做这种题首先就是建图,然后再对图进行操作。这道题解法是将友军之间相互距离全部算出来,最后使用最小生成树算法找到最小代价。

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
int n,m;
struct pp{
    int u;
    int v;
    int l;
};
int f[100];
char a[1003][1003];
int dx[]={0,0,-1,1},dy[]={-1,1,0,0};
map<pii,int> ma;
vector<pp> xd;
void bfs(int x,int y){
   int jl[1003][1003]={0};
   queue<pii> q;
   q.push({x,y});
   int bh=ma[{x,y}];
   jl[x][y]=0;
   while(!q.empty()){
        int x1=q.front().first,y1=q.front().second;
        q.pop();
        for(int i=0;i<4;i++){
            int xx=dx[i]+x1,yy=dy[i]+y1;
            if(xx<=0||xx>n||yy<=0||yy>m) continue;
            if(a[xx][yy]=='#'||jl[xx][yy]!=0) continue;
            q.push({xx,yy});
            jl[xx][yy]=jl[x1][y1]+1;
            if(a[xx][yy]=='*'){
                xd.push_back({bh,ma[{xx,yy}],jl[xx][yy]});
            }
        }
   }
}
bool cmp(pp x,pp y){
    return x.l<y.l;
}
int find(int x){
    if(f[x]==x) return x;
    return f[x]=find(f[x]);
}
signed main(){
   std::ios::sync_with_stdio(0);
   std::cin.tie(0);
   cin>>n>>m;
   int t=1;//给我军编号
   for(int i=1;i<=n;i++){
      for(int j=1;j<=m;j++) {
         cin>>a[i][j];
         if(a[i][j]=='*'){
            ma[{i,j}]=t;
            t++;
         }
      }
   }
   for(int i=1;i<=n;i++){
      for(int j=1;j<=m;j++){
        if(a[i][j]=='*'){
            bfs(i,j);
        }
      }
   }
   t--;
    sort(xd.begin(),xd.end(),cmp);
    for(int i=1;i<=55;i++) f[i]=i;
    int ans=0;
    for(int i=0;i<xd.size();i++){
        int x=find(xd[i].u),y=find(xd[i].v);
        if(x==y) continue;
        t--;
        f[x]=y;
        ans+=xd[i].l;
    }
    if(t>1){//至少有一个被完全包围
        cout<<"No"<<endl;
        return 0;
    }
    cout<<ans<<endl;
   return 0;
}

题目:A*BBBB(前缀和)

D-A*BBBB_河南萌新联赛2024第(二)场:南阳理工学院 (nowcoder.com)

解析:

求a*b;但a,b的值很大不能直接乘,需要转化为字符串来做,因为b每个位数相同,所以ai中与b单个字符相乘可以得到的数会从持续到从哪到哪lb(b字符串的长度)的长度,前缀和记录最后再遍历一遍前缀和,得到字符串,具体操作详见代码

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
void solve(){
    string a,b;
    cin>>a>>b;
    char b1=b[0];
    int bs=b[0]-'0';
    if(a=="0"||bs==0){
        cout<<"0"<<endl;
        return ;
    }
    int num[5000006]={0};
    int lb=b.size(),la=a.size();
    int da=lb+la;
    for(int i=la-1,j=1;i>=0;i--,j++){
        int as=a[i]-'0';
        int bj=as*bs;
        int xb=j;
        while(bj>0){
            int y=bj%10;
            num[xb]+=y;
            num[xb+lb]-=y;
            bj/=10;
            xb++;
        }
    }
    int sum=0;
    int sum1=0;
    char s[5000006];
    for(int i=1;i<=da;i++){
        sum+=num[i];
        sum1+=sum;
        int y=sum1%10;
        char c=y+'0';
        sum1/=10;
        s[i]=c;
    }
    int lg=0;
    for(int i=da;i>0;i--){
        if(lg==0&&s[i]=='0') continue;
        else {
            lg=1;
        }
        cout<<s[i];
    }
    cout<<endl;
}
signed main(){
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);
    int T;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

题目:“好”字符(进制哈希)

E-“好”字符_河南萌新联赛2024第(二)场:南阳理工学院 (nowcoder.com)

解析:

本题题意是求好字符,好字符的定义是:一个字符在字符串a中,在所有b的循环同构中所有a,b对应位置,如果一个字符在每个对应位置a,b要么全是此字符,要么全不是此字符。我们可以使用进制哈希来做,对每个存在的字符x当作1其余字符当作0,a字符串变为2*n的长度,从1开始到n,每次取n的长度,就相当于b每次的不同循环同构,用值来确定所有x字符出现的位置是否相同,有相同答案就加1;

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
#define ull unsigned long long
int n;
ull P=131;
ull h1[2000006],h2[1000006],p[2000006]={1};//h记录到每一位它的哈希值
void hx(string s,char a,ull *h){
   for(int i=1;i<s.size();i++){
    h[i]=P*h[i-1]+(s[i]==a);//相同为1;
   }
}
ull pd(ull l,ull r,ull *h){
    return h[r]-h[l-1]*p[r-l+1];
}
signed main(){
   std::ios::sync_with_stdio(0);
   std::cin.tie(0);
   cin>>n;
   string a,b;
   cin>>a>>b;
   set<char> se(a.begin(),a.end());
    a=' '+a+a;
    b=' '+b;
    int ans=0;
    for(int i=1;i<=2*n;i++) p[i]=P*p[i-1];
    for(auto x : se){
        hx(a,x,h1);
        hx(b,x,h2);
        for(int i=1;i<=n;i++){
            if(pd(1,n,h2)==pd(i,i+n-1,h1)) {//截取n的长度,用哈希值判断他们是否相等
                ans++;
                break;
            }
        }
    }
    cout<<ans<<endl;
   return 0;
}

题目:数独挑战

G-数独挑战_第二周算法入门班第六章习题:搜索与搜索剪枝 (nowcoder.com)

解析:

本题要对每行每列,以及每9个为一块,里面的数字都不相同,我们拿set记录是否出现,vector记录需要填数字的点,然后再对每个区域进行dfs遍历每一个可能出现的数,题目一定有解,所以所有点全部确立好了就退出遍历。

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
#define ull unsigned long long
set<int> mx[10];
set<int> my[10];
set<int> zg[3][3];
vector<pii> v;
int a[10][10];
int n;
int lg=0;
void dfs(int t){
    if(t>=v.size()) {
        lg=1;
        return;
    }
    int x=v[t].first,y=v[t].second;
    for(int i=1;i<10;i++){
        if(mx[x].count(i)||my[y].count(i)||zg[x/3][y/3].count(i)) continue;
        mx[x].insert(i),my[y].insert(i),zg[x/3][y/3].insert(i);
        a[x][y]=i;//cout<<t<<"?";
        dfs(t+1);
        if(lg) return;
        a[x][y]=0;
        mx[x].erase(i),my[y].erase(i),zg[x/3][y/3].erase(i);
    }
    return;
}
signed main(){
   std::ios::sync_with_stdio(0);
   std::cin.tie(0);
    n=9;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++) {
            cin>>a[i][j];
            if(a[i][j]!=0){
                mx[i].insert(a[i][j]);
                my[j].insert(a[i][j]);
                zg[i/3][j/3].insert(a[i][j]);
            }
            else{
                v.push_back({i,j});
            }
        }
    }
    dfs(0);
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            cout<<a[i][j];
            if(j!=n) cout<<" ";
        }
        cout<<endl;
    }
   return 0;
}

题目:[NOIP2010]关押罪犯(贪心,并查集

J-[NOIP2010]关押罪犯_第二周算法入门班第六章习题:搜索与搜索剪枝 (nowcoder.com)

解析:

本题题意是给两个房子分配人,使他们存在的最大怨恨值最小,如何分配,思路就是贪心将怨气大的两个人先分到不同的房子,遇到不得不让有怨恨值的人待在一起,这时怨恨值就是答案。

那么我们如何判断不得不将a,b他们放在一起呢?其实就是a,b之前有相同的怨恨目标x,并且和x的怨恨值更大。如何判断有相同怨恨目标呢?我们需要使用并查集,每次将a的敌人放在一起,b的敌人放在一起,当在后面敌人之间也有小怨恨值,敌人之间就不得不在一起了;

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
#define ull unsigned long long
int f[20004];
int dr[20004];
struct yh{
    int x;
    int y;
    int li;
};
bool cmp(yh x,yh y){
    return x.li>y.li;
}

int find(int x){
    if(f[x]==x) return x;
    return f[x]=find(f[x]);
}

void solve(){
    int n,m;
    cin>>n>>m;
    vector<yh> v;
    for(int i=0;i<m;i++){
        int x,y,z;
        cin>>x>>y>>z;
        v.push_back({x,y,z});
    }
    for(int i=1;i<=n;i++) f[i]=i;
    sort(v.begin(),v.end(),cmp);
    int ans=0;
    for(int i=0;i<v.size();i++){
        int x=v[i].x,y=v[i].y;
        int xx=find(x),yy=find(y);
        if(xx==yy){//如果之前有共同的敌人,但现在他们又是敌人了,不得不呆在一起,因为他们与之前共同的敌人怨气更大;
            cout<<v[i].li<<endl;
            return;
        }
        else{
            if(!dr[x]){//x没有敌人
                dr[x]=y;
            }
            else{//将x的敌人和y连起来(敌人的敌人就是朋友
                int a=find(dr[x]);//将他们连接
                if(a!=yy) f[yy]=a;
            }
            if(!dr[y]){
                dr[y]=x;
            }
            else{
                int b=find(dr[y]);
                if(b!=xx) f[xx]=b;
            }
        }
    }
    cout<<"0"<<endl;
    return;
}
signed main(){
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);
    int t;
    t=1;
    //cin>>t;
    while(t--){
        solve();
    }    
   return 0;
}

题目:Ubiquity

解析:

这道题原本以为不能使用搜索,但是发现它每个袋中球的和的乘积不超过10e5,所以可以将每个袋子的一个球枚举看乘积是否为x,这里没有规定有多少个袋子,所以我们可以将vector容器装一个袋子中的球,再用vector容器嵌套vector,作为袋子,搜索时就搜索每一个袋子。

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
#define ull unsigned long long
const int mod=1e9+7;
int n,x;
int len;
int ans=0;
vector<vector<int> > v;
void dfs(int t,int sum){
    if(t>=len){
        if(sum==x) ans++;
        return;
    }
    for(auto i : v[t]){
        int y=sum*i;
        //cout<<len<<endl;
        if(y>x) continue; 
        dfs(t+1,y);
    }
}
signed main(){
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);
    cin>>n>>x;
    for(int i=0;i<n;i++){
        int l;
        vector<int> v1;
        cin>>l;
        for(int i=0;i<l;i++){
            int y;
            cin>>y;
            v1.push_back(y);
        }
        v.push_back(v1);
    }
    len=v.size();
    dfs(0,1);
    cout<<ans<<endl;
   return 0;
}

题目:FG operation(容斥定理,快速幂

解析:

一个数学问题,只要序列所有数在0到9,而且必须要有一个0,和1个9,问这种序列有多少种,根据容斥定理,对n个数全排列,为10的n次,没有9的排列有9的n次,没有0的排列也有9的n次,但没有9的排列也可能没有0,没有0的排列也可能没有9,所以我们多减了一次即没有9,也没有0的排列,给其加上得到的就是答案。

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
#define ull unsigned long long
int const mod=1e9+7;
int ksm(int x,int a){//a的x次
    int res=1;
    while(x){//将x次二进制向右移,看末尾0,1;
        if((x&1)) res=(res*a)%mod;
        a=(a*a)%mod;
        x>>=1;
    }
    return res;
}
signed main(){
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);
    int n;
    cin>>n;
    int q1=ksm(n,10)%mod;
    int q2=ksm(n,9)*2%mod;//0没选或9没选
    int q3=ksm(n,8)%mod;//0和9都没有选的情况
    int ans=(mod+q1-q2+q3)%mod;
    cout<<ans<<endl;
   return 0;
}

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值