进阶训练赛(十二)

目录

问题 A: 符文宗师的魔方阵

问题 B: APP的成绩单

问题 C: 6.1.4.1 最大的节点

问题 D: 6.2.2.1 油田

问题 E: 6.3.2.1 电话网络

问题 F: 7.1.4.1 重型运输

问题 G: 打怪兽version-3

问题 H: 2.4.1 间谍

问题 I: 2.4.2 Web导航

问题 J: 2.4.3 骑士移动

问题 L: 吃菜

问题 M: 矩形


问题 A: 符文宗师的魔方阵

输入的时候记录一下每行每列有多少个-1,行数>-1的个数,可以遍历一遍,找到没有-1的那行计算出矩阵每行或每列的和,之后遍历一遍数组计算即可。

int tx[10],ty[10];//记录每行每列-1的个数
void solve()
{
    int a[10][10];
    for(int i=1;i<=5;i++){
        for(int j=1;j<=5;j++){
            cin>>a[i][j];
            if(a[i][j]==-1) tx[i]++,ty[j]++;
        }
    }
    int sum = 0;
    for(int i=1;i<=5;i++){
        if(tx[i]==0){
            for(int k=1;k<=5;k++) sum+=a[i][k];
            break; 
        }
    }

    //cout<<sum<<endl;
    for(int i=1;i<=5;i++){
        for(int j=1;j<=5;j++){
            if(a[i][j]==-1){
                int temp;
                if(tx[i]==1){
                    temp = 0;
                    for(int k=1;k<=5;k++){
                        if(a[i][k]!=-1) temp+=a[i][k];
                    }
                    a[i][j] = sum-temp;
                    tx[i]--,ty[j]--;
                }else if(ty[j]==1){
                    temp = 0;
                    for(int k=1;k<=5;k++){
                        if(a[k][j]!=-1) temp+=a[k][j];
                    }
                    a[i][j] = sum-temp;
                    tx[i]--,ty[j]--;
                }
            }
        }
        for(int j=5;j>=1;j--){
            if(a[i][j]==-1){
                int temp;
                if(tx[i]==1){
                    temp = 0;
                    for(int k=1;k<=5;k++){
                        if(a[i][k]!=-1) temp+=a[i][k];
                    }
                    a[i][j] = sum-temp;
                    tx[i]--,ty[j]--;
                }else if(ty[j]==1){
                    temp = 0;
                    for(int k=1;k<=5;k++){
                        if(a[k][j]!=-1) temp+=a[k][j];
                    }
                    a[i][j] = sum-temp;
                    tx[i]--,ty[j]--;
                }
            }
        }
    }

    for(int i=1;i<=5;i++){
        for(int j=1;j<=5;j++) printf("%3d ",a[i][j]);
        puts("");
    }
}

问题 B: APP的成绩单

利用结构体实现,根据学生每个信息的不同特征可以实现分类

typedef struct student{
    string name;
    string num;
    string gender;
    string goal;
}student;
void solve()
{
    int t;
    cin>>t;
    student s[100+10];
    for(int i=0;i<t;i++){
        string a[4];
        cin>>a[0]>>a[1]>>a[2]>>a[3];
        for(int j=0;j<4;j++){
            if(a[j]=="boy"||a[j]=="girl") s[i].gender = a[j];
            else if( (a[j][0]>='a' && a[j][0]<='z') ||(a[j][0]>='A' && a[j][0]<='Z')){
                s[i].name = a[j];
            }else if(a[j].length()==10) s[i].num = a[j];
            else s[i].goal = a[j];
 
        }
    }
    for(int j=0;j<t;j++){
        cout<<s[j].name<<" "<<s[j].num<<" "<<s[j].gender<<" "<<s[j].goal<<endl;
    }
 
}
 

问题 C: 6.1.4.1 最大的节点

题目描述容易想到用dfs去处理,但是这题数据量为1e5,如果从节点1~n依次搜索一遍,时间复杂度会达到O(n^2)。这题处理运用到了正难则反的思想,通过反向建立有向图,从节点下标最大的点开始搜索,找到哪些点可达,那么这么可达的点的最大标号就是搜索的起点。注意,在dfs中是不需要回溯的,这里推荐一下lxr学长讲dfs回溯的一篇文章,里面画的搜索树可以帮助理解:https://blog.csdn.net/m0_61735576/article/details/127814886?spm=1001.2014.3001.5502

我们反向建图从下标大的点搜索相当于搜索树从叶子往根部搜索,是不同分支合并到一条路的过程,是不用考虑选和不选的问题的。

const int N = 100000+10;
int idx,n,m,maxn;
int h[N],e[N],ne[N];
bool vis[N];
int res[N];
 
void add(int a,int b){
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
 
void dfs(int u)
{
    if(res[u]==0) res[u] = maxn;
    for(int i=h[u];i!=-1;i=ne[i]){
        int j = e[i];
        if(!res[j]) dfs(j);
    }
 
}
 
void solve()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    while(m--){
        int a,b;
        scanf("%lld %lld",&a,&b);
        add(b,a);
    }
    for(int i=n;i>=1;i--){
        //memset(vis,0,sizeof vis);
        if(!res[i]){
            maxn = i;
            dfs(i);
        }
    }
    for(int i=1;i<=n;i++) printf("%lld ",res[i]);
}

问题 D: 6.2.2.1 油田

这题还没明白为什么读入scanf("%s")会WA

迷宫问题用dfs和bfs搜索都可以,这题是典型的一类迷宫问题有固定的套路。存储完图后进行遍历,如果该点没被搜索过,就开始进行dfs,把所有和该点连通的点都标记完后,联通块数量+1。

int n,m;
char g[100+10][100+10];//存储图
bool vis[100+10][100+10];//标记是否访问过
PII dxy[8] = {{1,0},{-1,0},{0,-1},{0,1},{-1,-1},{-1,1},{1,-1},{1,1}};//八个方向

bool check(int x,int y)//检查是否走出地图
{
    if(x>=1 && x<=n && y>=1 && y<=m) return true;
    return false;
}

void dfs(int x,int y)
{
    vis[x][y] = true;
    for(int i=0;i<8;i++){
        int tx = x + dxy[i].first;
        int ty = y + dxy[i].second;
        if(check(tx,ty) && !vis[tx][ty] && g[tx][ty]=='@'){
            dfs(tx,ty);
        }
    }

}
void solve()
{
    while(cin>>n>>m){
        if(n==0 && m==0) break;
        memset(vis,0,sizeof vis);
        int ans = 0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++) cin>>g[i][j];
        }

        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(g[i][j]=='@' &&!vis[i][j]){
                    dfs(i,j);
                    ans++;
                }
            }
        }
        cout<<ans<<endl;
    }
    

}

问题 E: 6.3.2.1 电话网络

题目中描述的关键点其实就是我们离散数学中学到的割点,割点在书中的定义:在一个无向图中,如果有一个顶点集合,删除这个顶点集合以及这个集合中所有顶点相关联的边以后,图的连通分量增多,那么这个点集称为割点集。求关键点的个数就是求割点的个数。

处理有向图的强连通分量通常使用Tarjan算法,每个强连通分量作为搜索树中的一棵子树,搜索时,把当前搜索树中未处理的节点加入栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。

关于Tarjan求割点的算法模板,这里有相关的讲解:

割点(tarjan算法)_axtices的博客-CSDN博客_割点

const int maxn = 100+10;
int n,cnt,root; 
int head[maxn] , low[maxn],dfn[maxn],num;
bool cut[maxn];
struct Edge{
	int to , next;
}e[maxn*maxn];

void add(int u,int v){
	e[++cnt].next = head[u];
	e[cnt].to = v;
	head[u] = cnt;
}

void tarjan(int u){
	dfn[u] = low[u] = ++ num;
	int flag = 0;
	for(int i = head[u] ; i ; i = e[i].next){
		int v = e[i].to;
		if(!dfn[v]){
			tarjan(v);
			low[u] = min(low[u] , low[v]);
			if(low[v] >= dfn[u]){
				flag ++;
				if(u != root || flag > 1){ 
					cut[u] = true;
				}
			}
		}else{
			low[u] = min(low[u] , dfn[v]);
		}
	}
}

void init(){
	memset(head,0,sizeof head);
	memset(low,0,sizeof low);
	memset(dfn,0,sizeof dfn);
	memset(cut,false,sizeof cut);
	cnt = num = 0;
}

void solve()
{
    while(cin >> n && n){
		init();
		int u,v;
		while(cin >> u && u){
			while(1){
				char c = getchar();
				if(c == '\n') break;
				cin >> v;	
				add(u,v);
				add(v,u);
			}
		}
		for(int i = 1;i <= n ; i++){
			if(!dfn[i]){
				root = i;
				tarjan(i);
			}
		}
		int ans = 0;
		for(int i=1 ; i<= n ; i++){
			if(cut[i]) ans ++;	
		}
		cout << ans << endl;
	}
}

问题 F: 7.1.4.1 重型运输

题意为在无向图中,要求找到一条起点为1,终点为n且最小边权最大的通路,该通路的最小边权即最大承重。(通路的最小边权值即使构成通路所有边中权重最小的权值)

平常一般用dijkstra算法去求单源点最短路,这里可以通过改造dijkstra中的松弛不等式来解决题中问题。dist数组:记录从点1到当前点,允许通过的最大吨数。dits数组每次查找最大值进行比较,当有道路的最小值都比dist中的值大时,更新dist

网上还看见了用最大生成树来解决这道题的,具体链接:

POJ 1797 Heavy Transportation (最大生成树) - wuli涛涛 - 博客园

const int N = 1010;
int n,m;
int g[N][N];
bool vis[N];
int dist[N];//记录从1到当前点,允许通过的最大吨数
int dijkstra()
{
    for(int i=1;i<=n;i++) dist[i] = g[1][i];
    dist[1] = 0;
    for(int i=0;i<n;i++)
    {
        int t = -1;
        int maxn = -1;
        for(int j=1;j<=n;j++){
            if(!vis[j] && dist[j]>maxn ){
                maxn = dist[j];
                t = j;
            }
        }
        for(int j=1;j<=n;j++){
            if(dist[j]<min(dist[t],g[t][j]))
                dist[j] = min(dist[t],g[t][j]);
        }
        vis[t] = true;
    }

    if(dist[n]==0x3f3f3f3f) return -1;
    return dist[n];
}

void solve()
{
    int t;
    cin>>t;
    for(int i=1;i<=t;i++){
        cin>>n>>m;
        memset(g,-1,sizeof g);
        memset(vis,0,sizeof vis);
        while(m--){
            int a,b,c;
            cin>>a>>b>>c;
            g[a][b] = g[b][a] = max(g[a][b],c);
        }
        cout<<"Scenario #"<<i<<":"<<endl;
        cout<<dijkstra()<<endl<<endl;
    }
}

问题 G: 打怪兽version-3

暴力解决即可,从大到小排序,前k个元素不用计入总和

int a[200000+10];
bool cmp(int x,int y){
    return x>y;
}

void solve()
{
    int res = 0;
    int n,k;
    cin>>n>>k;
    for(int i=0;i<n;i++){
        cin>>a[i];
    }
    sort(a,a+n,cmp);
    for(int i=k;i<n;i++) res+=a[i];
    cout<<res;
    
}

问题 H: 2.4.1 间谍

在B个字符串中,找到在A中并且不在C中,按照先进先出原则输出

void solve()
{
    int a,b,c;
    while(cin>>a>>b>>c)
    {
        string s;
        queue<string>q;
        map<string,int>mp;
        map<string,int>mp1;
        for(int i=0;i<a;i++){
            cin>>s;
            mp1[s] = 1;
        }
        for(int i=0;i<b;i++){
            cin>>s;
            q.push(s);
        }
        for(int i=0;i<c;i++){
            cin>>s;
            mp[s] = 1;
        }
        bool flag = false;
        while(q.size()){
            auto t = q.front();
            q.pop();
            if(!mp[t] && mp1[t]){
                cout<<t<<" ";
                flag = true;
            }
        }
        if(!flag) cout<<"No enemy spy";
        cout<<endl;
        
    }
    
}
 

问题 I: 2.4.2 Web导航

可以直接用STL里的stack,也可以自己用string数组模拟

string v[100+10];
int hh,tt;//hh为栈顶,0为栈底
void solve()
{
    string s;
    v[0] = "***###.acm.org/";
    while(cin>>s){
        if(s[0]=='Q') break;
        if(s[0]=='V'){
            string str;
            cin>>str;
            v[++tt] = str;
            hh = tt;
            cout<<v[tt]<<endl;
        }else if(s[0]=='B'){
            if(tt<=0){
                cout<<"Ignored"<<endl;
            }else{
                cout<<v[--tt]<<endl;
            }
        }else if(s[0]=='F'){
            if((tt+1)>hh){
                cout<<"Ignored"<<endl;
            }else{
                cout<<v[++tt]<<endl;
            }
        }
    }
}

问题 J: 2.4.3 骑士移动

最短步数考虑用bfs解决,走日字和走上下左右有一些区别,一共有八个方向,用队列实现bfs,从起点开始搜索,每次更新当前点能到达的所有点,最短的路径会比其他的路径更早标记,搜索完地图后输出dist[x][y](x,y)为终点坐标,dist数组为当前点距离起点的距离

int n;
PII st,ed;//起点和终点
char g[300+10][300+10];//存储图
int dist[300+10][300+10];//从起点开始到当前位置一共走了几步
PII dxy[8] = {{-2,-1},{-1,-2},{1,-2},{2,-1},{2,1},{1,2},{-1,2},{-2,1}};//八个方向
bool check(int x,int y)//检查是否走出地图
{
    if(x>=1 && x<=n && y>=1 && y<=n) return true;
    return false;
}

int bfs()
{
    memset(dist,-1,sizeof dist);
    dist[st.first][st.second] = 0;
    queue<PII>q;
    q.push({st.first,st.second});
    while(!q.empty()){
        auto t = q.front();
        q.pop();
        for(int i=0;i<8;i++){
            int tx = t.first + dxy[i].first;
            int ty = t.second + dxy[i].second;
            if(check(tx,ty) && dist[tx][ty]==-1){//在图内并且没被访问过
                q.push({tx,ty});
                dist[tx][ty] = dist[t.first][t.second]+1;
            }
        }
    }
    return dist[ed.first][ed.second];
}

void solve()
{
    int t;
    cin>>t;
    while(t--){
        cin>>n;
        cin>>st.first>>st.second;
        cin>>ed.first>>ed.second;
        st.first++,st.second++,ed.first++,ed.second++;
        cout<<bfs()<<endl;
    }

}

问题 L: 吃菜

和01背包模板最大的区别是多了一道不受时间限制的菜可以选择。无论最终点菜顺序是什么,我们都可以将某一份菜安排在m时刻来点,那么就可以把背包容量扩充到 m+w[i]-1,问题转化成了总共有m-1的时间的01背包问题。

状态f[i][j]定义:前 i 个物品,背包容量 j 下的最优解(最大价值)

每次决定当前菜选或者不选:

不选:dp[i][j]=dp[i-1][j];
选:dp[i][j]=dp[i-1][j-s[i].w]+s[i].v;

注意:进行状态转移的时候,状态转移方程中:dp[x][y]与dp[x-1][y-w[i]]状态有关,要保证 y-w[i]这个状态是最优的。所以我们应该按w从小到大排一下序。

下面是朴素版没优化的二维版本

const int N = 3000+10;
int n,m;
struct node{
    int w;
    int v;
}s[3005]; //存储道菜的价值和吃这道菜所耗费的时间

bool cmp(node a,node b)
{
    return a.w<b.w;
}
int dp[3005][6005];
void solve()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>s[i].w>>s[i].v;
    sort(s+1,s+1+n,cmp);
    for(int i=1;i<=n;i++){
        for(int j=0;j<s[i].w+m;j++){
            dp[i][j]=dp[i-1][j];
            if(j>=s[i].w) dp[i][j]=max(dp[i-1][j],dp[i-1][j-s[i].w]+s[i].v);
        }
    }
    int res=0;
    for(int i=1;i<=n;i++) res=max(dp[n][m+s[i].w-1],res);
    cout<<res<<endl;
}
 

问题 M: 矩形

自己没什么思路,看了看别人的想法

将每个点 (x,y) 拆成两个点 x 和 y,再将 x 和 y 连一条无向边。

这样原问题就被抽象成了一个图论问题,而且还是个二分图。相应的操作就变成了已知 a—b,a—d,c—b,c—d 中的三条边,然后添加剩余的一条边。

二分图长这个样子:(图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻)

 

对于每个连通分量的答案(其实就是把当前联通分量对应的二分图变成完全二分图所需加的边数),我们可以用 dfs 来求出它在 x 方向上的点数和 y 方向上的点数,求出二者的乘积,就是相应的完全二分图的边数,再减去当前连通分量上的边数即可。

我们可以先计算出所有连通分量所对应的完全二分图的边数之和,再减去已知的边数,从而得到答案。

const int N = 100000+10;
vector<int> to[N*2];
bool vis[N*2];
vector<int>cnt;
void dfs(int u) {
    if (vis[u]) return;
    vis[u] = true;
    cnt[u/N]++;
    for (int it : to[u]) dfs(it);
}
void solve()
{
    int n;
    cin >> n;

    for(int i=0;i<n;i++){
        int x,y;
        cin>>x>>y;
        y += N;
        to[x].push_back(y);
        to[y].push_back(x);
    }

    ll ans = 0;
    for(int i=0;i<N*2;i++){
        if (vis[i]) continue;
        cnt = vector<int>(2);
        dfs(i);
        ans += (ll)cnt[0] * cnt[1];
    }
    ans -= n;
    cout<<ans<<endl;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届
### 回答1: 算法训练营进阶篇pdf 是一个提供算法进阶学习资料的电子书。这本书可以帮助算法学习者巩固和提高他们的算法能力。 首先,算法训练营进阶篇pdf 的内容丰富多样。它涵盖了各种算法主题,如动态规划、图论、贪心算法等。每个主题都有详细的讲解和示例代码,以帮助读者更好地理解和应用这些算法。 其次,这本书的难度适中。它适合已经具备一定算法基础的学习者进一步提高自己的能力。通过学习这本书,读者可以进一步提升自己解决复杂算法问题的能力,同时也可以深入了解各种常见的算法思想和技巧。 此外,这本书的编写方式也十分友好。作者在讲解算法原理的同时,还会结合具体的例子和图形进行说明,使得读者更容易理解和掌握算法的应用方法。同时,每个章节都有练习题目,可以帮助读者巩固所学的知识并提高实践能力。 总的来说,算法训练营进阶篇pdf 是一个很好的算法学习资料。它的内容丰富多样,难度适中,编写方式友好。读者通过学习这本书,可以提高自己的算法能力,掌握各种常见的算法思想和技巧,为解决实际问题提供了有力的工具和思路。 ### 回答2: 《算法训练营进阶篇pdf》是一份高级算法训练资料,旨在帮助读者深入学习和理解算法的高级概念和技巧。该资料包含了丰富的算法案例和问题,涵盖了动态规划、图论、字串处理等多个领域。 这份资料首先从基础的算法知识出发,引导读者逐步进阶。通过详细的讲解和示例代码,读者可以掌握各种高级算法的原理和实现方法。同时,该资料注重实践,提供了大量的练习题目,帮助读者巩固所学知识,并提供了解题思路和解决方法。 《算法训练营进阶篇pdf》的特点之一是注重实际应用。它涵盖了大量的真实场景和实际问题,并展示了如何用高级算法解决这些问题。这对于读者来说是具有很大帮助的,因为他们可以更好地理解算法在实际工程中的应用场景和效果。 此外,该资料还提供了丰富的参考文献和扩展阅读材料,帮助读者进一步拓宽和深入学习。同时,读者也可以通过该资料获得关于算法训练和职业发展的一些建议和指导。 总之,《算法训练营进阶篇pdf》是一份对于想要深入学习高级算法的读者来说非常有价值的资料。它适合那些已经具备了一定算法基础,但希望进一步提升能力和应用技巧的人。通过学习这份资料,读者将能够更好地应用高级算法解决实际问题,提高自己的算法能力,并在算法领域取得更高的成就。 ### 回答3: 《算法训练营进阶篇》PDF 是一个深入学习算法的进阶教材。该教材通过系统化的讲解和实践编程练习,帮助学习者更加深入地理解和应用算法。 该PDF内容主要涵盖了算法的进阶知识和应用,包括动态规划、贪心算法、图论、高级数据结构、字串处理等方面的内容。通过这些知识的学习,可以帮助学习者提高算法解决问题的能力,进一步提升编程的水平。 在《算法训练营进阶篇》PDF中,每个章节都有对应的理论讲解和算法实现的练习题。通过理论的学习,学习者可以掌握算法的核心思想和解题思路。而通过实践编程练习,可以帮助学习者巩固所学知识,提高算法编程的实际应用能力。 《算法训练营进阶篇》PDF的特点是内容丰富、逻辑性强、难度适中。无论是想进一步提高算法水平的程序员,还是希望在算法竞赛中取得好成绩的学生,都可以通过学习这本教材来达到自己的目标。 总之,《算法训练营进阶篇》PDF是一本非常实用和有价值的进阶教材,可以帮助学习者深入学习和应用算法,提高算法解决问题的能力,同时也为其他算法教学提供了参考和指导。推荐有一定算法基础的读者阅读并进行实践,相信会有很大的收获。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值