2022-BNUZ-IT节程序设计竞赛网络赛题解

IT节程序设计竞赛-网络赛题目解析

A-纯阳之体

解析

​ 本题是一个双指针问题,当然直接暴力也是可以做的。主要思想就是先求最长连续且不含重复字符的字串的长度。有了长度之后就可以再进行一次搜索,当长度等于最长长度的时候就可以进行一次输出了。

​ 双指针做法:使用使用两个指针, l l l r r r 并保证 l ≤ r l\le r lr r r r 指针向右移动,并记录下一个字符然后判断当前记录的字符是否出现过,没有出现过再判断当前长度是否比之前记录的长度长;要是出现则开始移动 l l l 指针,直到不出现为止。这样就可以找出最长的长度。然后使用同样的方法搜索,只不过这次只要当前长度等于搜索到的最长长度后就输出。

​ 暴力做法:枚举每一个区间,因为字符串最长只有2000,所以暴力是没有问题的。

AC代码

  • 暴力做法
#include <iostream>
#include <algorithm> 
using namespace std;
string str;
int hashh[100];
bool check(int l,int r){
	for(int i=l;i<=r;i++){
		if(hashh[str[i]-'A']!=0){
			return false;
		}else hashh[str[i]-'A']++;
	}
	return true;
}
int main(){
	cin>>str;
	int max_l,max_r;
	int maxx=0;
	for(int i=0;i<str.size();i++){
		for(int j=i+1;j<str.size();j++){
			for(int k=0;k<=30;k++)hashh[k]=0;
			if(check(i,j)){
				if(maxx<j-i-1){
					maxx=j-i-1;
				}
			}
		}
	}
	for(int i=0;i<30;i++)hashh[i]=0;
	
	for(int i=0;i<str.size();i++){
		for(int j=i+1;j<str.size();j++){
			for(int k=0;k<=30;k++)hashh[k]=0;
			if(check(i,j)){
				if(maxx==j-i-1){
					max_l=i,max_r=j;
					for(int k=max_l;k<=max_r;k++){cout<<str[k];}
					cout<<endl;
				}
			}
		}
	}
	return 0;
}
  • 双指针做法
#include <iostream>
using namespace std;
const int N=100;
int s[N];
string str;
int main(){
    cin>>str;
    int maxx=-1;
    int pos_l,pos_r;
    for(int i=0,j=0;i<str.size();i++){
        s[str[i]-'A']++;
        while(j<i&&s[str[i]-'A']>1)s[str[j++]-'A']--;
        maxx=max(maxx,i-j+1);
    }
    
    for(int i=0;i<30;i++)s[i]=0;
	for(int i=0,j=0;i<str.size();i++){
        s[str[i]-'A']++;
        while(j<i&&s[str[i]-'A']>1)s[str[j++]-'A']--;
        if(i-j+1==maxx){
			pos_r=i;
            pos_l=j;
			for(int k=pos_l;k<=pos_r;k++){cout<<str[k];}
			cout<<endl;
        }
    }
    return 0;
}

B-宝箱

解析

简单的一道思维题

每换一个宝箱可以增加一个体力

不断算出能兑换的宝箱数,并更新剩余体力数,直到剩下的体力数量小于3

AC代码

#include<bits/stdc++.h>
using namespace std;
int main() {
	int n;
	while (cin >> n)
	{
		int temp = n;

		int sum = 0;
		while (n >= 3) {
			sum += n / 3;
			n = n / 3 + n % 3;
		}
		cout << sum << endl;
	}
	return 0;
}

C-艰难的道路

解析

可以把这个三角形分为三条线段,对每一条线段思考。

用解析式表示一条线段,可以发现如果这条线段是一个一次函数,在数轴上一定可以找到一个点连接到这条线段而不经过三角形的内部。(如果延长这条线段,与数轴的交点以右的地方,都可以满足题目)

在这里插入图片描述

如果这条线段是一个常函数,与数轴就没有交点,就是不满足题意的点。

本题就转换为了一个求和 x轴平行的线的长度。

但是与y轴平行但是有一个点的y轴坐标在平行线的上边,也是安全的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7fVFLdeR-1650805177744)(file:///C:\Users\李俊霖\AppData\Local\Temp\ksohtml24756\wps2.jpg)]
除此之外的与x轴平行的都是不安全的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lnu7GfpB-1650805177745)(file:///C:\Users\李俊霖\AppData\Local\Temp\ksohtml24756\wps3.jpg)]

AC代码

#include <iostream>

using namespace std;
typedef long long ll;
const ll N = 5;

struct node {
    ll  x, y;
} a[N + 1];
int main() {
    ll  T;
    cin >> T;
    while (T--) {
        for (ll  i = 1; i <= 3; i++) {
            cin >> a[i].x >> a[i].y;
        }
        ll  p = -1, q = -1, s = -1;
        for (ll  i = 1; i <= 3; i++) {
            for (ll  j = i + 1; j <= 3; j++) {
                if (a[i].y == a[j].y && a[i].y && a[j].y) {
                    q = i, p = j, s = 6 - i - j;
                    break;
                }
            }
        }
        if (p == -1 || q == -1) {
            cout << 0 << "\n";
        } else {
            if (a[s].y <= a[p].y) {
                cout << abs(a[p].x - a[q].x) << "\n";
            } else {
                cout << 0 << "\n";
            }
        }
    }
    return 0;
}

D-活着的传奇

解析

先理解题目,会给出空part的编号和有几个广告词,将广告词尽可能分远的分到空part,使任意两个广告词之间的最小距离尽可能的大,问这个最小距离最大是多大

以给出的数据为例:

5 3
1 2 8 4 9

这五个空part可以任意选三个放广告词,可以是1 2 4,1 2 8 但是这样有的广告词就会挨的太近了,还有更远的分法:1 4 9

所以答案输出牛之间的最小距离(4-1)=3

我们先将空part进行排序,然后得出可能的最大距离为(max-min+1)/广告词的数量

从最大的距离到1开始遍历,查看每个广告词都在这个距离下是不是能全住进房间,能就输出这个距离,结束遍历,不能就继续遍历

这样做最坏的情况下的时间复杂度是n^2,数据规模是1,000,000,000,可能会时间超限

所以这里在查找距离的时候使用二分查找,时间复杂度是nlogn,

当然,在对房间号进行排序的时候也不能使用简单的冒泡排序等,那样时间复杂度也会变成n^2

这是使用c语言自带的排序,它在头文件stdlib.h里,是使用快速排序进行实现的,时间复杂度是nlogn

AC代码

#include <bits/stdc++.h>
using namespace std;
 
const int MAXN = 1e5;
int data[MAXN] = {};
 
int main(){
    int n, c;
    cin >> n >> c;
    int i;
    for (i=0; i<n; i++) {
        cin >> data[i];
    }
 
    //排序
    sort(data, data+n);
 
    //二分
    int left = 0;
    int right = data[n-1]-data[0];
    int mid;
    while (left<=right) {
        mid = left+(right-left)/2;
 
        //间隔mid的距离放置广告词,进行合法性检查
        int ad = 1;//可以放几个广告词
        int prev = data[0];//上一个广告词所在的空位
        for (i=1; i<n; i++) {
            if (data[i]-prev>=mid) {
                //符合要求,放一句广告词
                ad++;
                prev = data[i];
 
                //广告词数量够了,降低计算量
                if (ad>=c) {
                    break;
                }
            }
        }
 
        if (ad >= c) {
            left = mid+1;
        } else {
            right = mid-1;
        }
    }
 
    cout << right << endl;
 
    return 0;
}

E-小学二年级的数学题

解析

“小学二年级” 来源于毕导。

这道题需要利用三角形相似以及勾股定理。证明看图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ssDsBJay-1650805177746)(RBxajUfE912JXud.png)]

AC代码

#include<stdio.h>
#include<math.h>
int main(void) {
	int r, a, b, h, t;
	while (~scanf("%d", &t)) {
		while (t--) {
			scanf("%d %d %d %d", &r, &a, &b, &h);
			if (2 * r < b) {
				printf("YES\n");
			}
			else {
				double h1;
				double d = ((a - b) * r) / (2.0 * h);
				double x = sqrt(pow(r, 2) + pow(d, 2));
				double g = 0.5 * a - x;
				h1 = h - ((2.0 * g * h) / (a - b));
				printf("%.10lf\n", h1);
			}
		}
	}
}

F-收收心找个电子厂上班

解析

线段树的板子题,需要注意的是,区间更新的值需要用带模快速幂求,否则因为溢出导致答案错误。

暴力能过测试数据1~3,卡在测试数据4是因为没有使用带模快速幂,卡在测试数据5是因为没有使用线段树。

AC代码

  • C/C++
#include<iostream>
#include<math.h>

#define MAXN 100000
using namespace std;
typedef long long ll;
ll arr[MAXN + 5];
ll SUM = 0;

//快速幂
ll quickPow(ll a, ll b, ll p) { //带模快速幂
    ll ans = 1;
    while (b) {
        if (b & 1) ans = (ans * a) % p;
        b >>= 1;
        a = (a * a) % p;
    }
    return ans;
}

//线段树
//节点
struct node {
    int l, r;
    ll sum;
    ll tag;
} tree[MAXN * 4];

void pushUp(int idx) {
    tree[idx].sum = tree[idx * 2 + 1].sum + tree[idx * 2 + 2].sum;
}

//LazyTag
void pushDown(int idx) {
    int mid = (tree[idx].l + tree[idx].r) / 2;
    int leftNode = idx * 2 + 1;
    int rightNode = idx * 2 + 2;
    tree[leftNode].sum += (mid - tree[idx].l + 1) * tree[idx].tag;
    tree[rightNode].sum += (tree[idx].r - mid) * tree[idx].tag;
    tree[leftNode].tag += tree[idx].tag;
    tree[rightNode].tag += tree[idx].tag;
    tree[idx].tag = 0;
}

//生成树
void build(int idx, int l, int r) {
    tree[idx].l = l;
    tree[idx].r = r;
    if (l == r) {
        tree[idx].sum = arr[l];
    }
    else {
        int mid = (l + r) / 2;
        build(2 * idx + 1, l, mid);
        build(2 * idx + 2, mid + 1, r);
        pushUp(idx);
    }
}

//区间求和
void query(int idx, int l, int r) {
    if (l <= tree[idx].l && r >= tree[idx].r) SUM += tree[idx].sum;
    else {
        // TODO pushDown
        if (tree[idx].tag != 0) pushDown(idx);
        int mid = (tree[idx].l + tree[idx].r) / 2;
        if (r <= mid) query(idx * 2 + 1, l, r);
        else if (l > mid) query(idx * 2 + 2, l, r);
        else {
            query(idx * 2 + 1, l, r);
            query(idx * 2 + 2, l, r);
        }
    }
}

//区间更新
void update(int idx, int l, int r, ll val) {
    if (l <= tree[idx].l && tree[idx].r <= r) {
        tree[idx].sum += (tree[idx].r - tree[idx].l + 1) * val;
        tree[idx].tag += val;
        return;
    }
    if (tree[idx].tag != 0) pushDown(idx);
    int mid = (tree[idx].l + tree[idx].r) / 2;
    if (r <= mid) update(idx * 2 + 1, l, r, val);
    else if (l > mid)
        update(idx * 2 + 2, l, r, val);
    else {
        update(idx * 2 + 1, l, r, val);
        update(idx * 2 + 2, l, r, val);
    }
    pushUp(idx);
}

int main(void) {
    int n, m, o, x, y, a, b;
    while (cin >> n >> m) {
        for (int i = 1; i <= n; i++) {
            cin >> arr[i];
        }
        build(0, 1, n);
        for (int i = 0; i < m; i++) {
            cin >> o;
            if (o == 1) {
                cin >> x >> y;
                SUM = 0;
                query(0, x, y);
                cout << SUM << endl;
            }
            else if (o == 2) {
                cin >> x >> y >> a >> b;
                ll val = quickPow(a, b, 442333);
                update(0, x, y, val);
            }
        }
    }
    return 0;
}
  • JAVA
import java.io.IOException;
import java.util.Scanner;

public class Main {
    static Node[] tree;
    static long[] arr;
    static long SUM;

    public static void main(String[] args) throws IOException {
        Scanner input = new Scanner(System.in);
        int n, m, o, x, y, a ,b;
        while(input.hasNext()) {
            n = input.nextInt();
            m = input.nextInt();
            arr = new long[n+5];
            tree = new Node[4 * n];
            for(int i = 1; i <= n; i++) {
                arr[i] = input.nextLong();
            }
            build(0, 1, n);
            for(int i = 0; i < m; i++) {
                o = input.nextInt();
                if(o == 1) {
                    x = input.nextInt();
                    y = input.nextInt();
                    SUM = 0;
                    query(0, x, y);
                    System.out.println(SUM);
                } else {
                    x = input.nextInt();
                    y = input.nextInt();
                    a = input.nextInt();
                    b = input.nextInt();
                    long val = (int) quickPow(a, b, 442333L);
                    update(0, x, y, val);
                }
            }
        }
    }

    static long quickPow(long a, long b, long p) {
        long ans = 1;
        while(b != 0) {
            if((b & 1) != 0) {
                ans = (ans * a) % p;
            }
            b >>= 1;
            a = ((a * a) % p);
        }
        return ans;
    }

    static void pushUp(int idx) {
        tree[idx].sum = tree[idx * 2 + 1].sum + tree[idx * 2 + 2].sum;
    }

    static void pushDown(int idx) {
        int mid = (tree[idx].l + tree[idx].r) / 2;
        int leftNode = idx * 2 + 1;
        int rightNode = idx * 2 + 2;
        tree[leftNode].sum += (mid - tree[idx].l + 1) * tree[idx].tag;
        tree[rightNode].sum += (tree[idx].r - mid) * tree[idx].tag;
        tree[leftNode].tag += tree[idx].tag;
        tree[rightNode].tag += tree[idx].tag;
        tree[idx].tag = 0;
    }

    static void build(int idx, int l, int r) {
        tree[idx] = new Node();
        tree[idx].l = l;
        tree[idx].r = r;
        if(l == r) {
            tree[idx].sum = arr[l];
        } else {
            int mid = (l + r) / 2;
            build(2 * idx + 1, l, mid);
            build(2 * idx + 2, mid + 1, r);
            pushUp(idx);
        }
    }

    static void query(int idx, int l, int r) {
        if(l <= tree[idx].l && r >= tree[idx].r) SUM += tree[idx].sum;
        else {
            if(tree[idx].tag != 0) pushDown(idx);
            int mid = (tree[idx].l + tree[idx].r) / 2;
            if(r <= mid) query(idx * 2 + 1, l ,r);
            else if (l > mid) query(idx * 2 + 2, l ,r);
            else {
                query(idx * 2 + 1, l, r);
                query(idx * 2 + 2, l, r);
            }
        }
    }

    static void update(int idx, int l, int r, long val) {
        if(l <= tree[idx].l && tree[idx].r <= r) {
            tree[idx].sum += (tree[idx].r - tree[idx].l + 1) * val;
            tree[idx].tag += val;
            return;
        }
        if(tree[idx].tag != 0) pushDown(idx);
        int mid = (tree[idx].l + tree[idx].r) / 2;
        if (r <= mid) update(idx * 2 + 1, l, r, val);
        else if (l > mid)
            update(idx * 2 + 2, l, r, val);
        else {
            update(idx * 2 + 1, l, r, val);
            update(idx * 2 + 2, l, r, val);
        }
        pushUp(idx);
    }
}

class Node {
    int l, r;
    long sum;
    long tag;
}

G-辛苦的工作

解析

使用状态压缩DP

首先想下暴力算法,这里直接给出一个例子。
比如数据有 5 5 5 个点,分别是 0 , 1 , 2 , 3 , 4 0,1,2,3,4 0,1,2,3,4
那么在爆搜的时候,会枚举一下六种路径情况(只算对答案有贡献的情况的话):

c a s e 1 : 0 → 1 → 2 → 3 → 4 case 1: 0→1→2→3→4 case1:01234
c a s e 2 : 0 → 1 → 3 → 2 → 4 case 2: 0→1→3→2→4 case2:01324
c a s e 3 : 0 → 2 → 1 → 3 → 4 case 3: 0→2→1→3→4 case3:02134
c a s e 4 : 0 → 2 → 3 → 1 → 4 case 4: 0→2→3→1→4 case4:02314
c a s e 5 : 0 → 3 → 1 → 2 → 4 case 5: 0→3→1→2→4 case5:03124
c a s e 6 : 0 → 3 → 2 → 1 → 4 case 6: 0→3→2→1→4 case6:03214
那么观察一下 c a s e 1 case 1 case1 c a s e 3 case 3 case3,可以发现,我们在计算从点 0 0 0 到点 3 3 3 的路径时,其实并不关心这两中路径经过的点的顺序,而是只需要这两种路径中的较小值,因为只有较小值可能对答案有贡献。
所以,我们在枚举路径的时候,只需要记录两个属性:当前经过的点集,当前到了哪个点。
而当前经过的点集不是一个数。观察到数据中点数不会超过 20 20 20,我们可以用一个二进制数表示当前经过的点集。其中第 i i i 位为 1/0 表示是/否经过了点 i i i

​ 状态表示: f [ s t a t e ] [ j ] f[state][j] f[state][j]。其中 s t a t e state state 是一个二进制数,表示点集的方法如上述所示。

  • 集合:经过的点集为 s t a t e state state,且当前到了点 j j j 上的所有路径。

  • 属性:路径总长度的最小值

  • 状态计算:假设当前要从点 k k k 转移到 j j j,走到点 k k k 的路径就不能经过点 j j j,可以推出状态转移方程KaTeX parse error: Can't use function '\^' in math mode at position 27: … = min{f[state \̲^̲ (1 << j)][k] +…

所有状态转移完后,根据 f [ s t a t e ] [ j ] f[state][j] f[state][j] 的定义,要输出 f [ 111 ⋯ 11 ( n 个 1 ) ] [ n − 1 ] f[111⋯11(n个1)][n−1] f[11111(n1)][n1]
那么怎么构造 n n n 个 1 呢,可以直接通过 1 << n 求出 100 ⋯ 0 ( n 个 0 ) 100⋯0(n个0) 1000(n0),然后减一即可。

时间复杂度
枚举所有 s t a t e state state 的时间复杂度是 O ( 2 n ) O(2^n) O(2n)
枚举 j j j 的时间复杂读是 O ( n ) O(n) O(n)
枚举 k k k 的时间复杂度是 O ( n ) O(n) O(n)
所以总的时间复杂度是 O ( n 2 2 n ) O(n^22^n) O(n22n)

AC代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=20,M=1<<20;
int f[M][N],weight[N][N];
int n;
int main(){
    cin>>n;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            cin>>weight[i][j];

    memset(f,0x3f,sizeof f);
    f[1][0]=0;
    for(int i=0;i<1<<n;i++)
        for(int j=0;j<n;j++)
            if(i>>j&1)for(int k=0;k<n;k++)
				if(i-(1<<j)>>k&1)f[i][j]=min(f[i][j],f[i-(1<<j)][k]+weight[k][j]);

    cout<<f[(1<<n)-1][n-1];
    return 0;
}

H-方舱医院

解析

先从简单的开始找规律,可以画出当n=1,2,3,4时可以进行摆放的方式并统计数目,从中可知有竖放和横放两种方式,以竖放为例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UwsNS7ck-1650805177747)(../AppData/Roaming/Typora/typora-user-images/image-20220424165444981.png)]

AC代码

注:数组定义类型得为long long

#include <iostream>
using namespace std;
const int N=55;
int n;
long long ans[N]={0,1,2};
void init(){
	for(int i=3;i<=N;i++){
		ans[i]=ans[i-1]+ans[i-2];
	}
}
int main(){
	init();
	while(cin>>n){
		cout<<ans[n]<<endl;
	}
	return 0;
}

I-本来就是

解析

考点:动态规划
解题思路:

(1)我们可以先举一个比较简单的例子,假如只看给出样例的前两行:
7
3 8
显然应该从3和8中选择较大的,然后于7相加,便得出了最终结果。
(2)假如看给出样例的前三行:
7
3 8
8 1 0
显然,我们应该先从8和1中找出较大的与第二行的3相加,并且将结果保存;然后在1和0中找出较大的与第二行的8相加,并且将结果保存。这样就可以得到:
7
11 9
最终结果就是11和9中较大的数与7相加。

假设上面(1)中7为b[1] [1],那么3为b[2] [1],8为b[2] [2],可以发现新的b[1] [1]=b[1] [1]+max(b[2] [1],b[2] [2])。进一步分析,我们可以创建一个数组a用来保存每次新输入进去的数,数组b用来保存和上一层相加得到的结果,即b[1] [1]=a[1] [1]+max(b[2] [1],b[2] [2])。
而b和行无关只与列有关,因此可以将其设置为一维数组,即b[1]=a[1] [1]+max(b[1],b[2])。

AC代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
int num[102][102];            //存放原始地图 
int dp[102][102];            // dp 数组 
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int res = 0;
    int n;
    cin>>n;
    for(int i = 1;i <= n;++i)
        for(int j  = 1;j <= i;++j)
            cin>>num[i][j];
    // 对 dp 数组边界初始化        
    for(int i = 1;i <= n;++i)
        dp[n][i] = num[n][i];
    for(int i = n-1;i >= 1;i--)
        for(int j = 1;j <= i;j++)
            dp[i][j] = num[i][j] + max(dp[i+1][j],dp[i+1][j+1]);
    cout<<dp[1][1]<<endl;
    return 0;
}

J-沈阳大街东百往事

解析

这道题就是有n个点的树,n-1条带权无向边,m次询问,每次询问x,y之间的最短距离是多少. 方法一:floyd算法,由于询问所有点到所有点的最短距离,那么我们可以用floyd求出,floyd算法的复杂度为O(N^3),本来这道题的n最大为10000,询问为20000,那么该做法就会超时,但由于考虑到要让更多的人ak所以就改成n最大为20,m最大为100.

floyd代码:

#pragma warning(disable:4996)
#include <iostream>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
const int N = 1e4;
int n, m, d[8500][8500]; 
 
int main(){
	 	scanf("%d%d", &n, &m);
		for(int i = 0; i <= n; i ++)
	    	for(int j = 0; j <= n; j ++)
	    		if(i != j)d[i][j] = 0x3f3f3f3f;
	    		else d[i][j] = 0;
		
		for(int i = 1; i < n; i ++){
			int x, y, z;
			scanf("%d%d%d", &x, &y, &z);
			d[x][y] = d[y][x] = z;
		}
		
		//floyd
		for(int k = 1; k <= n; k ++){
	        for(int i = 1 ; i <= n; i ++){
	            for(int j = 1; j <= n ; j ++){
	                d[i][j] = min(d[i][j] , d[i][k] + d[k][j]);
	            }
	        }
	    }
	    
	    //询问 
		while(m --){
			int x, y;
			scanf("%d%d", &x, &y);
			printf("%d\n",d[x][y]);
		}	
    return 0;
}

方法二:dfs暴力

#include<bits/stdc++.h>
const int N = 50, M = 100;
int idx, h[N], edge[N], ne[N], w[N];
int ans = 0;

//加边操作
void add(int x, int y,int z)
{
	edge[idx] = y, ne[idx] = h[x],  w[idx] = z, h[x] = idx++;
} 


//u为当前结点,fa为父节点,k为目标结点,sum为总和
void dfs(int u, int fa,int k, int sum)
{  
	if(u == k)
	{
		ans = sum;  
		return;
	}
	for(int i = h[u]; ~i ; i = ne[i])
	{ 
		int j = edge[i]; 
		if(fa != j)
		{ 
			dfs(j, u, k, sum + w[i]);
		}
	}
}
int main()
{
	memset(h, -1, sizeof h); 
	int n, m;
	scanf("%d%d",&n,&m); 
	for(int i = 0; i < n - 1; i ++)
	{
		int x, y, z;
		scanf("%d%d%d",&x,&y,&z);
		add(x, y, z);
		add(y, x, z);
	}
	while(m --)
	{  
		int x, y ;
		scanf("%d%d",&x,&y);
		dfs(x, -1, y, 0); 
		printf("%d\n", ans);
	}
	
	return 0;
} 

方法三:倍增LCA + bfs(或者dfs), 我们先bfs一遍把从根节点到其他结点的距离d(i)求出来。由于是询问x到y的最短距离,且为一颗树,那么可知从根结点开始从上往下到达的每个结点为最短距离,设从根结点到x结点的距离为d(x),从根结点到y结点的距离为d(y),x和y的最近公共祖先为 d ( l c a ( x , y ) ) d(lca(x,y)) d(lca(x,y)) 那么x结点到y结点的距离就为 d ( x ) − d ( y ) − 2 ∗ d ( l c a ( x , y ) ) d(x)-d(y)-2*d(lca(x,y)) d(x)d(y)2d(lca(x,y)).

AC代码

#pragma warning(disable:4996)
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e4 + 100;
int n, m;
int h[N], ne[2 * N], e[2 * N], w[2 * N], idx;
int depth[N], fa[N][20];
int a[N];
//加边操作 
void add(int a, int b, int c) {
    e[idx] = b; w[idx] = c; ne[idx] = h[a]; h[a] = idx++;
}

//bfs计算出各结点的深度 
void bfs() {
    memset(depth, 0x3f, sizeof(depth));
    queue<int>q;
    q.push(1);
    depth[0] = 0;
    depth[1] = 1;
    while (!q.empty()) {
        int t = q.front(); q.pop();
        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (depth[j] > depth[t] + 1) {
                depth[j] = depth[t] + 1;
                fa[j][0] = t;
                q.push(j);
                for (int k = 1; k <= 15; k++)
                    fa[j][k] = fa[fa[j][k - 1]][k - 1];
            }
        }
    }
}

//倍增lca求出a和b的最近公共祖先 
int lca(int a, int b) {
    if (depth[a] < depth[b])swap(a, b);
    for (int k = 15; k >= 0; k--)
        if (depth[fa[a][k]] >= depth[b])
            a = fa[a][k];
    if (a == b) return a;
    for (int k = 15; k >= 0; k--)
        if (fa[a][k] != fa[b][k]) {
            a = fa[a][k];
            b = fa[b][k];
        }
    return fa[a][0];
}

//bfs计算出各结点到根结点的距离 
int vis[N];
void bfs2() {
    queue<int> q;
    q.push(1);
    a[1] = 0;
    vis[1] = 1;
    while (!q.empty()) {
        int t = q.front(); q.pop();
        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (vis[j] == 1)continue;
            vis[j] = 1;
            a[j] = a[t] + w[i];
            q.push(j);
        }
    }
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    for (int i = 1; i < n ; i++) {
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        add(x, y, z);
        add(y, x, z);
    }
    bfs2();
    bfs();
    while (m--) {
        int x, y;
        scanf("%d%d", &x, &y);
        printf("%d\n", a[x] + a[y] - 2 * a[lca(x, y)]);
    }
    return 0;
}

K-可难到派蒙了

解析

这题主要是考察双指针+字符串拼接,每次标记被统计的字符,并将出现次数与对应位的字符串拼接成新的字符串。

AC代码

#include <bits/stdc++.h>
#include <string>
#include <iostream>
#include <cstring>
using namespace std;

int main() {
    string a, str;
    cin >> a;
    int n;
    cin >> n;
    while (n--) {
        int len = a.length();
        str = "";
        for (int i = 0; i < len; i++) {
            int j = i + 1, cnt = 1;
            while (a[i] == a[j]) {
                j++;
                cnt++;
            }
            str += to_string(cnt);
            str += a[i];
            i = j - 1;//因为进入循环以后,i会++
        }
        a = str;
    }
    cout << a << endl;
    return 0;
}

L-森酱的泡泡堂

解析

解题思路: 方法一:暴力法,枚举每一个点,然后把该点的行和列上的元素都加起来。复杂度 O ( N 3 ) O(N^3) O(N3),但还不符合题目的要求因为题目的数据范围是 N , M < = 3000. O ( N 3 ) N,M<=3000.O(N^3) N,M<=3000.O(N3) 1 0 9 10^9 109级别的。 暴力枚举(会超时):

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
ll n, m, a[3050][3050],b[3005] = {0},c[3005] = {0};
 
//把第x行,第y列的元素都加起来 
ll cal(int x,int y){
	ll res = 0;
	for(int i = 0; i < m; i ++) res += a[x][i];
	for(int i = 0; i < n; i ++) res += a[i][y];
	return res;
} 
 
int main(){
	
    while(scanf("%lld%lld",&n,&m)!=EOF){
    memset(b, 0, sizeof b);
    memset(c, 0, sizeof c);
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            scanf("%lld",&a[i][j]);
        }
    }
    ll ans=0;
    //枚举每一个点 
     for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
        	//更新最大值 
            ans = max(ans, cal(i, j) - a[i][j]);
         }
     }
     
    printf("%lld\n",ans);	
	}
    return 0;
}

方法二: 由于我们枚举每个点之后,要把每个点对应的行和列都加起来,那么易知每个行和列相加的操作是可以预处理的,比如说我们在枚举(2,2)这个点的时候把第二行(设第二行元素的总和为sum1)和第二列(设第二列元素的总和为sum2)的所有元素都加了起来,在枚举(2,3)这个点的时候又遍历并计算了第二行的元素总和(sum1),那么我们可以把这个行和列的操作预处理出来(即用数据sum1代表每一行的总和,sum2代表每一列的总和,如sum1[x]代表第x行的总和,sum2[y]代表第y列的总和.)那么我们在枚举每个点的时候只需要计算 s u m 1 [ i ] + s u m 2 [ j ] − a [ i ] [ j ] sum1[i] + sum2[j] - a[i][j] sum1[i]+sum2[j]a[i][j]就可以了.复杂度为 O ( N 2 ) = 1 0 6 O(N^2) = 10 ^ 6 O(N2)=106是不会超时的.

预处理法:

AC代码

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
ll a[3050][3050],sum1[3005] = {0},sum2[3005] = {0};
int main(){
	ll n,m;
    while(scanf("%lld%lld",&n,&m)!=EOF){
    //初始化 
    memset(sum1, 0, sizeof sum1);
    memset(sum2, 0, sizeof sum2);
    
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            scanf("%lld",&a[i][j]);
        }
    }
    
    //预处理每一行的总和 
     for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
              sum1[i]+=a[i][j];  
         }
     }
     //预处理每一列的总和 
    for(int i=0;i<m;i++){
        for(int j=0;j<n;j++){
              sum2[i]+=a[j][i];  
         }
     }
     //枚举每个点 
    ll ans=0;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            ans=max(ans,sum1[i]+sum2[j]-a[i][j]);
        }
    }
    printf("%lld\n",ans);	
	}
    return 0;
}

M-ONE PIECE

解析

本题是一道贪心题。

​ 可以发现,可以控制第一个火炬亮灭的只有同时操作1,2火炬,可以控制第二个火炬亮灭的只有操作1,2或2,3。
​ 知道了这样的性质后可以发现,因为相同的操作是无意义的,所以每组火把的操作与否是可以直接确定的。
​ 可以按照顺序比对,要是第一个火炬和目标火炬不同,就进行1,2操作。这样就将第一个火把固定了,就可以看第二个火把是否相同,相同不管,不同就操作2,3。
​ 按照上述类推,最后比到n-1。然后进行一个循环判定是否全部都和目标相同即可。

AC代码

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1010;
int n;
char start[N],aim[N];

struct res{
    int a;
    int b;
}edge[100100];
void turn(int i){
    if (start[i]=='1')start[i]='0';
    else start[i]='1';
}
int main(){
    cin>>start>>aim;
    n=strlen(start);
    int res=0;
    for(int i=0;i<n-1;i++)
        if(start[i]!=aim[i]){
            edge[res].a=i;
            edge[res].b=i+1;
            turn(i),turn(i+1);
            res++;
        }
    int flag=1;
    for(int i=0;i<n;i++){
        if(start[i]!=aim[i]){
            flag=0;
            break;
        }
    }
    if(flag){
        cout<<res<<endl;
        for(int i=0;i<res;i++){
            cout<<edge[i].a+1<<" "<<edge[i].b+1<<endl;
        }
    }
    else cout<<"114514yuanshi"<<endl;
    return 0;
}

N-炸鱼

解析

题意:将整数 n分成 k 份,问有多少种不同的分法

这道题我们可以用dp:

f[i][x] 表示 i 分成 x 个非空的数的方案数。

显然 i<x 时 f[i][x]=0 , i=x 时 f[i][x]=1;

其余的状态,我们分情况讨论:

①有1的 ②没有1的

第一种情况,方案数为 f[i-1][x-1]

第二种情况,方案数为 f[i-x][x] (此时 i 必须大于 x)

所以,状态转移方程为: f[i][x]=f[i-1][x-1]+f[i-x][x]

AC代码

#include<bits/stdc++.h>
using namespace std;

int n,k,f[201][7];  //f[k][x] k 分成 x 份 ={f[k-1][x-1],f[k-x][x]}

int main(){
    cin >> n >> k;
    
	for (int i=1;i<=n;i++) {f[i][1]=1;f[i][0]=1;}
	for (int x=2;x<=k;x++) {f[1][x]=0;f[0][x]=0;}  // 边界,为了防止炸,把有0的也处理了
    
	for (int i=2;i<=n;i++)
        for (int x=2;x<=k;x++)
            if (i>x) f[i][x]=f[i-1][x-1]+f[i-x][x];
            else f[i][x]=f[i-1][x-1];
    
	cout<<f[n][k];
    return 0;
}

O-叛逆期的lxl

解析

如果直接创建一个数组去跑,时间和空间复杂度都是不被允许的。

需要找出行与列的规律,把时间复杂度空间复杂度降为O(1)。

还有需要注意的是,由于 n,m 的范围是1e6,n * m存在超过 32 位整数类型的结果,需要用 64 位整数类型来存。

AC代码

#include<iostream>

using namespace std;

int main(void) {
	ios::sync_with_stdio(false);
	long long t, n, m, x;
	while (cin >> t) {
		while (t--) {
			cin >> n >> m >> x;
			long long col = x / n;
			long long row = x % n == 0 ? n - 1 : x % n - 1;
			long long ans = row * m + col;
			ans += x % n == 0 ? 0 : 1;
			cout << ans << endl;
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值