2020年首届算法竞赛网络挑战赛直播讲解课程

比赛链接

菜鸡的我,第四名。。在这里插入图片描述

A 矛盾激化

题意

给定地图,这个地图有两个出口,现在我们需要求出从所有点到任意一个出口的距离中的最短路径的最大值
本题为输出答案题,给定你一种情况,然后输出它的答案

题解

如果实在不会,可以手查(滑稽)
如果直接从每个点开始搜一遍最短距离不现实
因为只有两个出口,所以我们可以从出口出发然后跑两边BFS,每个点都维护一个距离的最小值
有几个坑点:
1.从出口往里面跳的时候只能跳一个格子
2.从别的格子跳往其他格子时只能跳两个格子
3.跳两格的时候还要判断中间隔过去的一格是不是空格
4.输入的格子有些恶心人

代码

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>

const int INF = 2147483647;

using namespace std;

int n, m, sx[3], sy[3], cnt, ans;
char map[208][90];
int dis[208][90];
int dx[5] = {0, 2, 0, -2};
int dy[5] = {2, 0, -2, 0};

int rx[5] = {0, 1, 0, -1};
int ry[5] = {1, 0, -1, 0};
struct node {
    int x, y, sum;
}temp, now;
queue<node> Q;
bool vis[208][90];

inline void BFS(int num) {
    memset(vis, 0, sizeof(vis));
    while (!Q.empty()) Q.pop();
    vis[sx[num]][sy[num]] = 1;
    temp.sum = 0, temp.x = sx[num], temp.y = sy[num];
    Q.push(temp);
    while (!Q.empty()) {
        now = Q.front();
        int x = now.x, y = now.y, s = now.sum;
        Q.pop();
        dis[x][y] = min(s, dis[x][y]);
        for(int i=0; i<4; i++) {
            int xx = dx[i]+x, yy = dy[i]+y;//走两格 
            int zx = rx[i]+x, zy = ry[i]+y;//走一格 
            if(!vis[xx][yy] && map[xx][yy] == ' ' && xx <= 2*n+1 && xx > 0 && yy <= 2*m+1 && yy > 0 && map[zx][zy] == ' ' && (x != sx[num] || y != sy[num])) {
            	//普通跳两格 
                vis[xx][yy] = 1;
                temp.x = xx, temp.y = yy, temp.sum = s+1;
                Q.push(temp);
            }
            if(!vis[zx][zy] && map[zx][zy] == ' ' && x == sx[num] && y == sy[num] && zx <= 2*n+1 && zy <= 2*m+1 && zx > 0 && zy > 0) {
            	//当从出口往里跳 
                vis[zx][zy] = 1;
                temp.x = zx, temp.y = zy, temp.sum = s+1;
                Q.push(temp);
            }
        }
    }
}

int main() {
    scanf("%d%d", &m, &n);
    for(int i=1; i<=2*n+1; i++) {
        for(int j=1; j<=2*m+1; j++) {
            dis[i][j] = INF;
        }
    }
    gets(map[0]);
    for(int i=1; i<=2*n+1; i++) {
        gets(map[i]+1);
        for(int j=1; j<=2*m+1; j++) {
            if(i == 1 || j == 1 || i == 2*n+1 || j == 2*m+1)//边界 
                if(map[i][j] == ' ') {//记录出口 
                    sx[++cnt] = i;
                    sy[cnt] = j;
                }
        }
    }
    BFS(1);
    BFS(2);//从两个出口开始bfs 
    for(int i=1; i<=2*n+1; i++) {
        for(int j=1; j<=2*m+1; j++) {
            if(dis[i][j] != INF)
                ans = max(ans, dis[i][j]);
        }
    }
    printf("%d", ans);
}

B 我去前面探探路

题意

对于一个树,我们可以进行两种操作,第一种:选择一个节点,与该节点相连的边会被标记。第二种:选择第一个节点,与该节点相连的边会被标记,同时与该节点相连的节点的相连的边也会被标记。这两种操作有各自的花费。问将所有边标记所需花费是多少

题解

树形dp
我们可以发现,在儿子节点操作2,与自己操作1对父亲节点的影响一样的
也就是这两种情况在后效性上是等价的
也就是他自己其实也可以被对兄弟操作2给影响
我们可以得到:

dp[u][0] :以点u为根的子树下的边全部被覆盖,且没有向u节点上方覆盖。也就是u不放,u的子节点只能进行操作1

dp[u][1]:以点u为根的子树下的边全部覆盖,且向上覆盖长度为1。u进行操作1,或者u为空,u的子节点进行操作2

dp[u][2]:以点u为根的子树下的边全部覆盖,且向上覆盖长度为2。u进行操作2

dp[u][3]:以点u为根的子树的子树里的边都被覆盖,但是u和子树间的边不一定被覆盖。也就是父亲节点进行操作2
v是u的儿子节点
dp[u][3]是为了处理 1-2-3-4-5-6-7-8-9,c1=10,c2=15这种极端的情况,只要c2覆盖3和7,就能覆盖所有的边

dp[u][j]是不同情况 覆盖以u为节点的子树的最小代价

int t=min(dp[v][0],min(dp[v][1],dp[v][2]));
        dp[u][0]+=dp[v][1];    //u的子节点v是1,u才能是0(向上覆盖长度0),不能有2,否则就会向上覆盖
        dp[u][1]+=t;  //向上覆盖长度是1,有两种选择: 1.在u上放置c1,v上1,2,3随意   2.u上不放,在一个v上放置c2,然后其他的v上随意,
        dp[u][2]+=min(t,dp[v][3]);  //u放置了c2,子节点v处于什么状态(0,1,2,3)都行
        dp[u][3]+=t;    //因为是状态3,所以u和v之间的边被它的父节点覆盖,所以v不受上面影响,可以当作一个单独的树看待
        optimal=min(optimal,dp[v][2]-t);  //其中一个v上放置c2,选择增加费用最小的一个v

代码

#include<iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn=10005,maxc=1005,INF=0x3f3f3f3f;
int n,c1,c2,a,b,dp[maxn][4];
//dp[u][v],v=2,1,0,把放置的装置看作势,也就是v的值,然后势向周围逐层减小
// 当u上放置c2,v=2,u上放置c1或者离c2一跳是v=1,当u离c1一跳或者c2两跳时,v=0
//当v=0时,u的子节点不能全为0,u可以放置东西,如果一个点上内有多个v值,取最高的
//
//dp[u][0]为在u不部署,而且父节点不为0的情况
// dp[u][1]为在u部署C1,dp[u][2]为在u部署C2
// dp[u][3]为在u不部署,而且父节点为0的情况,也就是说,子节点有一个必须为2
// 覆盖以u为节点的子树的最小代价

//dp[u][0]为覆盖子树,且向上覆盖长度为0,就是u不放,u的子节点只能放c1
// dp[u][1]为覆盖子树,且向上覆盖长度为1,就是在u放置c1,或者在u的子节点放c2
// dp[u][2]为覆盖子树,且向上覆盖长度为2,也就是在u放置c2
// dp[u][3]为u的儿子节点为根的子树被全部覆盖,但是u和它的儿子之间的边可能没被完全覆盖(就是说父节点上有c2,所以不需要)
// [3]是为了处理  1-2-3-4-5-6-7-8-9,c1=10,c2=15这种极端的,只要c2覆盖3和7,就能覆盖所有的边
// 覆盖以u为节点的子树的最小代价
vector<int> graph[maxn];

void dfs(int u,int fa){
    dp[u][0]=0;
    dp[u][1]=c1;
    dp[u][2]=c2;
    dp[u][3]=0;
    if(graph[u].empty()) return;
    int optimal=INF,total=0;
    for(auto iter=graph[u].begin();iter!=graph[u].end();++iter){  //遍历子节点
        int v=*iter;
        if(v==fa) continue;
        dfs(v,u);
        int t=min(dp[v][0],min(dp[v][1],dp[v][2]));
        dp[u][0]+=dp[v][1];    //u的子节点v是1,u才能是0(向上覆盖长度0),不能有2,否则就会向上覆盖
        dp[u][1]+=t;  //向上覆盖长度是1,有两种选择: 1.在u上放置c1,v上1,2,3随意   2.u上不放,在一个v上放置c2,然后其他的v上随意,
        dp[u][2]+=min(t,dp[v][3]);  //u放置了c2,子节点v处于什么状态(0,1,2,3)都行
        dp[u][3]+=t;    //因为是状态3,所以u和v之间的边被它的父节点覆盖,所以v不受上面影响,可以当作一个单独的树看待
        optimal=min(optimal,dp[v][2]-t);  //其中一个v上放置c2,选择增加费用最小的一个v
    }
    total=optimal+dp[u][3];
    dp[u][1]=min(dp[u][1],total);
}

int main(void){
    while(cin>>n>>c1>>c2 && n){
//        memset(dp,INF,sizeof(dp));
        for(int i=1;i<=n;++i) graph[i].clear();
        for(int i=1;i<n;++i){
            scanf("%d%d",&a,&b);
            graph[a].push_back(b);
            graph[b].push_back(a);
        }
        dfs(1,-1);
        printf("%d\n",min(dp[1][0],min(dp[1][0],dp[1][1])));
    }
}




C 数列

题意

在这里插入图片描述

题解

题目中说最后的时候a=0,b=0,c=0作为结束标志
最后一行的元素一定是上一行的答案
那我们就可以逆着往前推导
根据公式
在这里插入图片描述
lastans就是上一行的答案,把下一行的三个式子带入第一行,就可以求出公式,可以解出再上个提问的答案。。。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=50004;
const int man=5e5+3;
ll w[maxn];
struct node{
	ll a,b,c;
}x[man];
ll a1;
ll sum[man];
int main()
{
//	freopen("in.txt","r",stdin);
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&w[i]);
	}
	int tot=1;
	while(~scanf("%lld%lld%lld",&x[tot].a,&x[tot].b,&x[tot].c))
	{
	tot++;
//	if(x[tot-1].a==-3&&x[tot-1].b==-3&&x[tot-1].c==-3)break; 
	}
	tot--;
	sum[tot]=-x[tot].a;
	for(int i = tot-1; i;i--) {
		a1 = (0ll + w[sum[i+1]] * sum[i+1] + w[sum[i+1]] *w[sum[i+1]] * (sum[i+1] + 1) + 1);
        if(a1 == 0) sum[i] = 1;
        else sum[i] = -(0ll + x[i].a * (sum[i+1] + 1) * w[sum[i+1]] * w[sum[i+1]] + w[sum[i+1]] * sum[i+1] * (x[i].b + 1) + x[i].c + sum[i+1]) / a1;
    }
	for(int i=2;i<=tot;i++)
	{
		printf("%lld\n",sum[i]);
	}
	return 0;
	
}

D 点名

题目

在体育课上,同学们常常会迟到几分钟,但体育老师的点名却一直很准时。
老师只关心同学的身高,他会依次询问当前最矮的身高,次矮的身高,第三矮的身
高,等等。在询问的过程中,会不时地有人插进队伍里。你需要回答老师每次的询问

题解

使用大根堆和小根堆来解决第k小的问题
建立一个大根堆qu2(大元素在上面),建立一个小根堆qu1
然后大根堆qu2来维护第k小
在这里插入图片描述
每次有学生入队(新增元素进入我们的两个堆)
我们先让学生进入大根堆qu2,之后让qu2中最大的元素进入小
根堆qu1,这一步目的是为了时刻保持我们大根堆qu2中最大值是我们的第k大,也时刻保持qu2中元素只有k个。
如果我们想求第k+1大,只需将qu1中的最小值调入我们qu2中
。我们qu2中的最大值就是我们的第k+1大。

代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
typedef long long ll;
using namespace std;
const int maxn=3e4+9;
long long a[maxn];
long long b[maxn];
priority_queue <ll,vector<ll>,greater<ll> >q1;
priority_queue <ll,vector<ll>,less<ll> >q2;
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%lld",&b[i]);
	}
	for(int i=1;i<=m;i++)
	{
		for(int j=b[i-1]+1;j<=b[i];j++)
		{
			q2.push(a[j]);
			q1.push(q2.top());
			q2.pop();
		}
		q2.push(q1.top());
		q1.pop();
		printf("%lld\n",q2.top());
	}
	return 0;
}

E 蚂蚁

F 耐久度

G 内存管理

H 采集香料

I 青蛙过河

J 膜法区间

posted @ 2020-07-29 10:49  回归梦想  阅读(146)  评论(0编辑  收藏  举报