2022—SWJTU-寒假ACM校队选拔赛第三场-题解

A - A

算法分析

要想让S(x + 1) < s(x) 只能是发生在进位的时候,因此每逢尾数为 9 时就会对答案产生贡献。

AC code

#include<bits/stdc++.h>
using namespace std;
void solve()
{
    int n,res = 0;
    cin >> n;
    if(n % 10 == 9) res += 1;
    res += n / 10;
    cout << res << endl;
}
int main()
{
    int T;
    cin >> T;
    while(T --)
    solve();
    
    return 0;
}

B - B

算法分析

一看这题面就有背包问题的影子。但是直接背包的话复杂度必定爆炸,因此我们考虑怎么优化优化。

本题需要注意到的第一个点就是问的是子序列而不是子串,因为数之间是可以隔开的。

然后题目要求的是从 n 的序列中找元素之和能够被 m 整除的非空子序列

我们可以注意到一个地方,若 n> m ,那么对于这个序列的前缀和而言,根据抽屉原理,由于s[1],s[2],s[3].....s[n] 对 m 取模的结果必定是在[0,m - 1]的范围内,那么最坏的情况也已经保证了[0,m - 1] 区间内的数至少各有一个,那么必定存在 i,j 使得s[i] % m == s[j] % m 那么 i 至 j 的这一段必定会被 m 整除,因此满足了题意。(不在最坏情况的话,那么数已经出现重复,必然会满足题意)

那么我们再看看 n = m 的情况, ,那么最坏的情况也已经保证了 0 一定会出现,那么已经达成条件。

那么最后再看看n < m 的情况,直接背包就完事了

状态表示

f[i][j] 表示从前 i 个数中选一个子集,使得其和 %m 以后为 j 的方案数,那么最后本题目的答案就是f[n][0] 是否大于 2 ,(因为存在空集的可能性)

状态转移

f[i][j] = f[i - 1][j] + f[i - 1][(j - a[i] + m)%m]

就是考虑是否选择第 i 个数

第 i 个数若是不选的话,那么可以直接从f[i - 1][j] 转移过来

第 i 个数若是选的话,那么在第 i-1 的时候的取模值为 (j - a[i]) % m 才行,考虑到取模的溢出记得加上 m

问题就解决了,上代码。

注意一个细节,f[i][j] = f[i - 1][j] + f[i - 1][(j - a[i] + m)%m]转移的过程中会因为方案数过多,炸范围,因为当f[i][j] >= 2 时,我们都将其赋值为 2 即可

AC code

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

void solve()
{
    int n,m;
    cin >> n >> m;
    LL a[n + 1],f[m + 1][m + 1];
    for(int i = 1;i <= n;i ++) scanf("%lld",&a[i]),a[i] %= m;
    
    if(n >= m) puts("YES");
    else 
    {
        memset(f,0,sizeof f);
        f[0][0] = 1;
        for(int i = 1;i <= n;i ++)
         for(int j = 0;j <= m - 1;j ++)
         {
             f[i][j] = f[i - 1][j] + f[i - 1][(j - a[i] + m) % m];
             if(f[i][j] >= 2) f[i][j] = 2;
         }
        if(f[n][0] == 2) puts("YES");
        else puts("NO");
    }
    
}
int main()
{
    int T = 1;
    //cin >> T;
    while(T --)
    solve();
    
    return 0;
}

C - C

算法分析

简单思维题 没啥好写的

AC code

#include<bits/stdc++.h>
using namespace std;
void solve()
{
    int n,sum = 0;
    cin >> n;
    int a[n + 1];
    for(int i = 1;i <= n;i ++) scanf("%d",&a[i]),sum += a[i];
    
    if(sum < n) puts("1");
    else if(sum == n) puts("0");
    else cout << sum - n << endl;
    
}
int main()
{
    int T;
    cin >> T;
    while(T --)
    solve();
    
    return 0;
}

D - E

算法分析

组合数学

进行 k 次移动后,至多会产生 min(k,n - 1) 个空房间,那么设当前产生了 i 个空房间,那么也就是说现在要将 n 个人,分配到 n-i 个房间中去,利用隔板法。

n 个人之间有n-1个可插隔板的位置,要分成 n-i 份则需要 n-i-1 块挡板,那么就是_{n - 1}^{n- i-1}\textrm{C} = _{i}^{n-1}\textrm{C} ,再去考虑从 n 个房间中选出 i 个空房间为 _{n}^{i}\textrm{C}

答案就是对 _{n}^{i}\textrm{C} * _{n - 1}^{i}\textrm{C} 求和就完事了

AC code

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int mod = 1e9 + 7; 
LL fac[N],inv[N];
LL qmi(LL a, LL k, LL p)  // 求a^k mod p
{
    LL res = 1 % p;
    while (k)
    {
        if (k & 1) res = res * a % p;
        a = a * a % p;
        k >>= 1;
    }
    return res;
}

void fc(int n)
{
    inv[0] = fac[0] = 1;
    for(int i = 1;i <= n;i ++) fac[i] = (i * fac[i - 1]) % mod,inv[i] = qmi(fac[i],mod - 2,mod);
}

LL c(LL a,LL b)
{
    return fac[a] * inv[b] % mod * inv[a - b] % mod;
}

void solve()
{
    LL n,k;
    cin >> n >> k;
    k = min(k,n - 1);
    fc(N);
    LL res = 0;
    for(int i = 0;i <= k;i ++)
	    res = (res + c(n,i) * c(n - 1,i) % mod) % mod;
    cout << res << endl;
}
int main()
{
    int T = 1;
    //cin >> T;
    while(T --)
    solve();
    
    return 0;
}

E - F

算法分析

题目求的是加边的方案数,使得其最短距离不改变。

我们来一起回忆一下,学最短路的时候是怎么思考的

dist [i,j] + dist [j,k] => dist [i,k]

通过不断松弛,使得其最短。

那么这题可以借用这样子思想去思考

我们已经确定了起点和终点,那么我们只需要枚举 起点到 i 的距离 + 终点到 j 的距离 + i,j 之间的距离判断其是否大于等于最短距离,如果满足那么这个 i,j 组合就是合法的加边结果。

那么这样子思路就很简单了,我们对起点和终点分别跑两次最短路,枚举要加边的两个端点,判断其是否满足条件,计算其对答案的贡献即可。

AC code

#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;

int vis[N][N];
vector<int> g[N];
int dist_s[N],dist_t[N];

typedef long long LL;

void solve()
{
    int n,m,s,t;
    cin >> n >> m >> s >> t;
    while(m --)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        g[a].push_back(b);
        g[b].push_back(a);
        vis[a][b] = vis[b][a] = 1;
    }
    
    memset(dist_s,- 1,sizeof dist_s);
    memset(dist_t,- 1,sizeof dist_t);
    
    queue<int> q;
    q.push(s);
    dist_s[s] = 0;
    while(q.size())
    {
        auto u = q.front();
        q.pop();
        for(int i = 0;i < g[u].size();i ++)
        {
            int v = g[u][i];
            if(dist_s[v] == -1) dist_s[v] = dist_s[u] + 1,q.push(v);
        }
    }
    
    q.push(t);
    dist_t[t] = 0;
    while(q.size())
    {
        auto u = q.front();
        q.pop();
        for(int i = 0;i < g[u].size();i ++)
        {
            int v = g[u][i];
            if(dist_t[v] == -1) dist_t[v] = dist_t[u] + 1,q.push(v);
        }
    }

    LL res = 0;
    for(int i = 1;i <= n;i ++)
     for(int j = i + 1;j <= n;j ++)
      if(!vis[i][j] && dist_s[i] + 1 + dist_t[j] >= dist_s[t] && dist_t[i] + 1 + dist_s[j] >= dist_s[t])
       res ++;
    cout << res << endl;
}
int main()
{
    int T = 1;
    while(T --) solve();
    return 0;
}

F - G

算法分析

又双叒叕是我们亲爱的线段树喽,可以说是每场必出的考点了

就是拿线段树去维护前 K 个的最小花费即可

AC code

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
vector <pair <int,int> >in[maxn],out[maxn];
struct NODE
{
    int l,r;
    long long sum,num;
} p[maxn*4];
int n,m,k;
void build(int l,int r,int cur)
{
    p[cur].l=l;
    p[cur].r=r;
    p[cur].num=p[cur].sum=0;
    if(l==r)
        return ;
    int mid=(l+r)/2;
    build(l,mid,cur*2);
    build(mid+1,r,cur*2+1);
}
void pushup(int k)
{
    p[k].num=p[k*2].num+p[k*2+1].num;
    p[k].sum=p[k*2].sum+p[k*2+1].sum;
}
void update(int num,int pri,int cur)
{
    int l=p[cur].l,r=p[cur].r;
    if(l==r)
    {
        p[cur].num+=num;
        p[cur].sum+=(long long)num*pri;
        return ;
    }
    int mid=(l+r)/2;
    if(pri<=mid)
        update(num,pri,2*cur);
    else
        update(num,pri,2*cur+1);
    pushup(cur);
}
long long query(int num,int cur)
{
    int l=p[cur].l,r=p[cur].r;
    if(l==r)
        return (long long)l*min(p[cur].num,(long long)num);
    if(num<=p[cur*2].num)
        return query(num,cur*2);
    else
        return p[cur*2].sum+query(num-p[cur*2].num,cur*2+1);
}
int main()
{
    int l,r,x,y;
    scanf("%d%d%d",&n,&k,&m);
    for(int i=0; i<=n; i++)
    {
        in[i].clear();
        out[i].clear();
    }
    for(int i=1; i<=m; i++)
    {
        scanf("%d%d%d%d",&l,&r,&x,&y);
        in[l].push_back(make_pair(x,y));
        out[r].push_back(make_pair(x,y));
    }
    long long ans=0;
    build(1,maxn,1);
    for(int i=1; i<=n; i++)
    {
        for(int j=0; j<in[i].size(); j++)
            update(in[i][j].first,in[i][j].second,1);
        ans+=query(k,1);
        for(int j=0; j<out[i].size(); j++)
            update(-out[i][j].first,out[i][j].second,1);
    }
    printf("%lld\n",ans);
    return 0;
}


G - I

算法分析

构造题,首先考虑不可能的情况

样例中就给了我们很好的提示, d>2h 时必定不成立(srds 这个也是显而易见的)

然后还有一个就是 d= 1 时,节点数只能是 2

然后再考虑怎么建树

我的第一想法是先构建成一条满足条件的链,然后不够的再往上加点即可

比如说一颗高度为 h ,最大长度为 d 的一颗树,我们只需要在 1 号结点一侧连接 h 个点,另外一侧连接 d-h 个点,这里就会构造成一个节点数为 d+1 的链,然后剩余的结点为了防止超过最大长度直接加到 1 结点上就行了,因为倘若会有剩余的结点说明构造完 h 个点后另外一侧至少存在一个点,那么加到 1 结点上必然是不会超的。

 然后有个特殊的情况就是h==d 的情况,这是我 debug 的时候才发现的,构造 8,2,2 的时候结果不对,于是考虑到了h==d 的情况,h==d 意味着构造完一侧后另外一侧不允许再加边了,但是此时 n 还有剩余的话怎么办呢,给他全加到 2 节点上就搞定了。

AC code

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
void solve()
{
    int n,d,h;
    cin >> n >> d >> h;
    if(d > 2 * h || (d < 2 && n != 2))
    {
        puts("-1");
        return ;
    }
    
    if (h == d)
	{
		for (int i = 1;i <= h;i ++)
		{
			printf("%d %d\n",i,i + 1);
		}
		for (int i = h + 2;i <= n;i ++)
		{
			printf("%d %d\n",2,i);
		}
		return ;
	}
    else 
    {
        for(int i = 1;i <= h;i ++)
        printf("%d %d\n",i,i + 1);
        
        if(d - h >= 1)
        {
            printf("%d %d\n",1,h + 2);
            
            for(int i = 1;i <= d - h - 1;i ++)
            {
                printf("%d %d\n",h + 1 + i,h + 2 + i);
            }
            
            if(n - d - 1 >= 1)
            {
                int left = n - d - 1;
                for(int i = d + 2;left > 0;i ++)
                {
                    printf("%d %d\n",1,i);
                    left --;
                }
            }
        }
    }
}
int main()
{
    int T = 1;
    //cin >> T;
    while(T --)
    solve();
    
    return 0;
}

H - J

算法分析

将一个长度为偶数的区间分割为两部分,且两部分元素异或和相同

看到两部分元素异或和相同,那么可以想到这两部分异或后的结果是 0 

那么本题见变成了 对于一段区间 [L,R] 来说要使得他的异或值为 0, 那么就是

[0,L - 1] \wedge [0,R ] =[0,L-1]\wedge [0,L-1]\wedge [L,R] = [L,R] = 0

那么就是统计有多少个区间使得  [0,L-1] == [0,R] 即可

然后最后注意一下,区间长度是偶数这个限制条件即可

AC code

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 2e6 + 10;
typedef long long LL;

LL a[N],b[N];
LL vis_odd[N],vis_even[N];

void solve()
{
    LL n,tmp = 0;
    cin >> n;
    LL res = 0;
    vis_even[0] ++;//初始状态的前缀为 0,且为偶数(区间为空)
    for(int i = 1;i <= n;i ++)
    {
        int x;
        cin >> x;
        tmp = tmp ^ x;
        if(i % 2 == 1)
        {
            res += vis_odd[tmp];
            vis_odd[tmp] ++;
        }
        else 
        {
            res += vis_even[tmp];
            vis_even[tmp] ++;
        }
    }
    cout << res << endl;
}
int main()
{
    int T = 1;
    //cin >> T;
    while(T --) solve();
    return 0;
}

I - K

算法分析

线性dp

为啥想到这题是dp呢,因为这题长得就很dp(bushi)

很明显的就是让你求解整个序列上的极值问题,然后判断其是否无后效性和是否存在最优子结构就能够知道是不是个 dp ,这题整个序列上的极值显然是可以由其子序列转移而来的,具体证明就不多赘述,直接开始dp

状态表示

f[i] 表示前 i 个数字中取 \frac{n}{2} (向下取整)个数并且这些数两两不相邻,其最大的和

状态转移

由于是\frac{n}{2} (向下取整)存在奇偶数不同的情况,因此我们对其分类讨论

若是奇数并且选了第 i 个数则 f[i] = f[i - 2] + a[i] ,因为选了第 i 个数后,第 i-1 个数就不能被选中了,只能从f[i - 2] 转移而来

若没有选第 i 个数则f[i] = f[i - 1]

若是偶数并且选了第 i 个数则同理, f[i] = f[i - 2] + a[i] ,

 若没有选第 i 个数,我们只能将奇数位置上的数全选上才能有 \frac{n}{2} (向下取整)个数字,不然的话若还是 f[i-1] ,此时 i-1 为奇数除二下取整会比原来少一个就不符合题目的条件了。

不妨我们举个例子来看看

ex.1 2 3 4

n = 4 那么我们总共需要取 2 个数

若我们取了 4,那么根据转移方程我们要从f[i - 2] 转移那么就是f[2] ,f[2] 中可以取到 1 个数,共计两个满足条件

那么若我们不取 4 那么我们需要从f[3] 中取出两个数来,但是这是不符合我们状态方程的定义的,而要取出两个数的唯一办法就是取奇数位上的数字 1,3 才能满足条件

 因此转移方程表示成了 f[i] = s[i - 1] ,s 表示为奇数位次上的前缀和

for(int i = 2;i <= n;i ++)
	{
		if(i % 2 == 1)
		{
			f[i] = max(f[i - 2] + a[i],f[i - 1]);
		}
		else 
		{
			f[i] = max(f[i - 2] + a[i],s[i - 1]);
		}
	}

AC code

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
LL a[N],f[N],s[N];
void solve()
{
	int n;
	cin >> n;
	for(int i = 1;i <= n;i ++) scanf("%lld",&a[i]);
	s[1] = a[1];
	for(int i = 3;i <= n;i += 2) s[i] = s[i - 2] + a[i];
	
	for(int i = 2;i <= n;i ++)
	{
		if(i % 2 == 1)
		{
			f[i] = max(f[i - 2] + a[i],f[i - 1]);
		}
		else 
		{
			f[i] = max(f[i - 2] + a[i],s[i - 1]);
		}
	}
	cout << f[n] << endl;

}
int main()
{
	int T = 1;
	while(T --)
	solve();
	return 0;
}

写在最后

由于时间原因和作者本身能力问题,本题解难免会出现部分纰漏,还希望大家多多批评指正。

码字不易,点个赞再走吧

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
### 回答1: SWJTU计算机网络实验是指西南交通大学计算机网络课程的实践部分。该实验旨在帮助学生更好地理解和应用计算机网络理论知识,培养学生的实际动手能力和问题解决能力。 在SWJTU计算机网络实验中,学生需要完成一系列与计算机网络相关的实际操作和实验任务。这些任务包括网络配置、协议设置、网络性能测试等。学生将学习如何搭建和配置网络环境,理解网络协议的原理和实现方式,通过实际操作感受网络性能的变化和差异。 此外,SWJTU计算机网络实验还注重培养学生的团队合作精神和沟通能力。在实验中,学生通常需要分组合作完成任务,并要求进行组内交流和合作。通过协作完成任务,学生学会了如何与他人进行有效的沟通和协作,提高了解决问题的能力。 SWJTU计算机网络实验的目标是使学生在实践中深入理解计算机网络的原理和技术,并通过实验任务提升学生的实践能力和解决问题的能力。通过这些实验,学生可以更好地应用所学知识解决实际问题,为今后的学习和就业打下坚实的基础。 ### 回答2: SWJTU计算机网络实验是指西南交通大学计算机网络课程中的实践部分。该实验旨在让学生通过实际操作和调试,掌握计算机网络的基本原理和技术。 在SWJTU计算机网络实验中,学生将学习并实践诸如网络拓扑设计、网络设备配置、网络协议实现和网络故障排除等内容。实验中,学生将使用模拟器软件来创建和配置一个小型的计算机网络环境,通过模拟真实网络中的各种情况来进行实验。 实验内容包括但不限于以下几个方面:首先,学生需要了解和学习计算机网络的基本概念和原理,例如网络协议、TCP/IP模型、网络拓扑结构等。其次,学生需要了解如何使用模拟器软件来创建一个网络拓扑,并配置相应的网络设备,例如路由器、交换机等。然后,学生需要学习和实践网络协议的配置和实现,例如IP地址分配、路由设置、数据包转发等。最后,学生需要学习和实践网络故障排除的方法和技巧,例如使用命令行工具进行网络诊断和故障隔离。 通过SWJTU计算机网络实验,学生可以提升自己的实践能力和沟通能力。在实验过程中,他们会遇到各种网络问题和故障,需要自己进行分析和解决。此外,学生还可以与同学一起合作完成实验任务,共同解决实验中遇到的问题。 总之,SWJTU计算机网络实验是一项重要的实践环节,通过实际操作和调试,学生可以更好地掌握计算机网络的基本原理和技术。通过实验,学生可以提升自己的实践能力和团队合作能力,为今后的学习和工作打下良好的基础。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cold啦啦啦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值