NENU SUMMER TRAINING #3 (题解)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ShadowGhostH/article/details/81212011

A

题意

输出 1378 n 的个位数字是多少?

分析

我们知道对于一个数的n次幂,他的个位数字会以一种循环节的形式呈现。原因是个位数字只有十个,当十个用完的时候,回到最初的两个数相乘,一定会出现一个循环节。这次我比较懒,直接手写出了 8 n 的个位循环节情况为 8 , 4 , 2 , 6 ,然后根据n模4的取余情况确定个位数字即可。

代码

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

int a[] = {8, 4, 2, 6};

int main(){
    int n;
    scanf("%d", &n);
    if(!n) puts("1");
    else printf("%d\n", a[(n-1)%4]);

    return 0;
}

B

题意

给出 n 个数,求这 n 个数中,两个数异或后,结果为 x 的组合种数有多少?即满足 a i a j = x ( i < j ) 的个数。

分析

对于异或操作我们需要知道: x x = 0 ,若 x y = z , 则 x z = y
那么题目就转化为了给出任意一个数 a i ,查询 a i x 的个数,或者 a i a i x 两两组合的组合数有多少种 。
考虑到题目的数据范围很小,只有 1 e 5 ,所以我们可以直接对每个数的出现情况计数,然后直接组合求解。需要注意的是,由于组合的下标不能相等,所有如果出现 x = 0 的情况,需要对组合进行额外的判断。如果数据的取值范围增大,我们也可以用 s e t 来完成操作。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;

int a[maxn], cnt[maxn];

int main(){
    int n, k;
    ll ans = 0;
    memset(cnt, 0, sizeof cnt);
    scanf("%d%d", &n, &k);
    for(int i=0; i<n; i++){
        scanf("%d", &a[i]);
        cnt[a[i]]++;
    }

    for(int i=0; i<maxn; i++){
        if(cnt[i] && (i^k)<maxn && cnt[i^k]){
            if(k==0){
                ans += (ll)cnt[i] * (ll)(cnt[i]-1) / 2;
            }
            else {
                ans += (ll)cnt[i] * (ll)cnt[i^k];
                cnt[i] = cnt[i^k] = 0;
            }
        }
    }

    printf("%I64d\n", ans);

    return 0;
}

C

题意

每个人都有且只有一个自己喜欢的人,他们在玩一个游戏,问能不能找到一个数 k , 使得从任意一个人 x 开始,沿着他们喜欢的人的链往后找 k 个人可以找到 y ,游戏重新从y开始,再往后找 k 个人可以找到 x 找出最小的 k ,如果不能,组输出 1

分析

首先我们进行模型的转化,我们认为人是独立的点,他们之间喜欢的关系为一条有向边,以此来建图。难么游戏的本质就是,从一个点出发,沿着唯一的一条有向路径(所有节点的出度为 1 ),经过 2 k 的长度后回到了自己。
而回到自己的概念是什么?图上存在有向环。而因为需要在图上落脚,即 k 2 应当是有意义的。所以当环的长度为偶数时,我们取环长的一半,为奇数时我们取环长,然后取所有环长的最小公倍数 ( l c m ) 。即为最小的k。
什么时候无解呢?就是环上有点的入度大于1, 即有点不能回到自己的时候。我选择了用dfs判环并记录路径长度。

代码

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


 const int maxn = 105;

 int a[maxn], ans;
 bool vis[maxn], can;

 int gcd(int x, int y){
    return y==0?x:gcd(y, x%y);
 }

 void dfs(int x, int s, int len){
    //cout<<x<<" "<<s<<" "<<len<<" "<<ans<<endl;
    if(vis[x]){
        if(x==s) {
            if(len&1)
                ans = ans*len/gcd(ans, len);
            else {
                len /= 2;
                ans = ans*len/gcd(ans, len);
            }
        }
        else can = false;
    }
    else {
        vis[x] = true;
        dfs(a[x], s, len+1);
    }
 }

 int main()
 {
     int n;
     memset(vis, false, sizeof vis);
     ans = 1;
     can = true;
     scanf("%d", &n);
     for(int i=1; i<=n; i++){
        scanf("%d", &a[i]);
     }

     for(int i=1; i<=n; i++){
        if(!vis[i]){
            dfs(i, i, 0);
        }
     }

     if(can) printf("%d\n", ans);
     else printf("-1\n");

 }

D

题意

n 个漂亮女孩,每个人有一个魅力值和体重,有一个舞台,舞台的承重最大为 w 。现在知道女孩儿们有朋友圈,朋友圈里的朋友,和自己也是一个朋友圈的,有m条朋友之间的关系。对于每一个朋友圈而我们只能请朋友圈中的所有人,或者朋友圈中的一个人,当然也可以不请。问,在保证舞台可以承担的条件下,最大可以获得魅力值是多少。

分析

朋友圈中的朋友 的朋友,和自己是一个朋圈的,这个概念我们可以很自然地联想到dfs求联通块,或者并查集求分组。
而对于在限定空间的条件下,取更多的价值,我们自然而言的想到了背包问题。只是这里不同的是,由于分组的不同我们所选用的策略也是不同的,那么我们用到的就是分组背包的思想(这里不做详细说明)。
需要注意的一点是,由于没有说朋友圈内不会再给出朋友关系,所以需要特殊判断一下。emmmm,本来是个小操作,不过因为我自己忽视了这点结果wa了两发就很难受了。(并查集+分组背包)

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

const int maxn = 1005;
struct Node{
    int fa;
    int wg;
    int be;
    int sumw;
    int sumb;
}a[maxn];
int dp[maxn][maxn], vis[maxn];
int found(int x) {
    int p = x;
    while(a[p].fa!=p)
        p = a[p].fa;
    while(x!=p) {
        int temp = a[x].fa;
        a[x].fa = p;
        x = temp;
    }
    return p;
}

void mex(int x, int y){
    int fax = found(x), fay = found(y);
    if(fax==fay) return;
    a[fax].fa = fay;  // fax->fay
    a[fay].sumb += a[fax].sumb;
    a[fay].sumw += a[fax].sumw;
}

int main()
{
    int n, m, w;
    memset(vis, false, sizeof vis);
    scanf("%d%d%d", &n, &m, &w);
    for(int i=1; i<=n; i++) {
        scanf("%d", &a[i].wg);
        a[i].sumw = a[i].wg;
        a[i].fa = i;
    }
    for(int i=1; i<=n; i++){
        scanf("%d", &a[i].be);
        a[i].sumb = a[i].be;
    }

    for(int i=1; i<=m; i++){
        int u, v;
        scanf("%d%d", &u, &v);
        mex(u, v);
    }

    dp[0][0] = 0;
    int cc = 1;
    for(int j=1; j<=n; j++){
        int now = found(j);
        if(vis[now]) continue;
        vis[now] = true;

        for(int i=1; i<=w; i++){
            dp[cc][i] = dp[cc-1][i];
            if(a[now].sumw <= i)
                dp[cc][i] = max(dp[cc][i], dp[cc-1][i-a[now].sumw]+a[now].sumb);
        }

        for(int t=1; t<=n; t++){
            if(found(t)!=now) continue;
            for(int i=1; i<=w; i++){
                if(a[t].wg <= i){
                    dp[cc][i] = max(dp[cc][i], dp[cc-1][i-a[t].wg]+a[t].be);
                }
            }
        }
        cc++;
    }

    int ans = 0;
    for(int i=w; i>=0; i--){
        ans = max(dp[cc-1][i], ans);
    }
    printf("%d\n", ans);

    return 0;
}

E

题意

已知有 n 个机场,编号 1 ~ n ,机场有两种类型: 0 1 ,机场 i 飞往机场 j , i j 类型相同那么花费为 0 类型不同则花费 | i j |
现给出机场 a , b a b 的最小花费.

分析

两种情况:
1. 若 a , b 类型相等,那么最小花费为 0
2. 若 a , b 类型不等,那么最小花费为 1 (因为相同类型机场花费为 0 ,总会存在一对不同类型机场相邻的情况: 01 这样子,从相邻的机场跳过去就可回到第一种情况。花费只有在改变机场类型时候产生 1 的花费。

代码

#include <cstdio>
#include <cstring>
const int maxn=1e5+10;
char w[maxn];
int main()
{
    int n,a,b;
    scanf("%d%d%d%s",&n,&a,&b,w+1);
    if(w[a]==w[b])  puts("0");
    else  puts("1");

}

F

题意

定义操作:
一个数列 s 初始值是 1 ,进行如下操作来形式成一个新数列:“ s m e x ( s ) s
m e x ( s ) 为数列 s 中没有出现的最小整数。

s t e p 1 : 1
s t e p 2 : 1   2   1 2 m e x s 0 ))
s t e p 3 : 1   2   1   3   1   2   1 3 m e x s 1 ))
s t e p 4 : 1   2   1   3   1   2   1   4   1   2   1   3   1   2   1 4 m e x s 3 ))
现给出询问,回答第 n 次操作后形成的序列的第 k 个数字是什么。

分析

首先,显然该数列是递归定义的。我们也可以递归的求解。
因为这个序列是每次倍增的,所以对应位置的数也是指数式的。因此我们可以寻找到k的固定位置为 2 n 1 值固定为 n
又因为数列是对称的,那么如果k在前一半,直接递归,如果是在后一半,那么先减一半(对称过去)再递归。

代码

#include <iostream>
typedef long long ll;
using namespace std;
int meo(ll n, ll k){
    if (k == (1ll<<n-1))    return n;
    return solve(n-1, k<(1ll<<n-1)?k:k-(1ll<<n-1));
}

int main(){
    ll n, k;
    cin >> n >> k;
    cout << meo(n,k) << endl;
    return 0;
}

G

题意

已知 n ,求满足 2 n = 1 x + 1 y + 1 z x , y , z

分析

此题为构造题。观察式子本身容易得知 2 n = 1 n + 1 n
不妨设 x = n 那么只需要求出
1 n = 1 y + 1 z 的解。此时我们可以设 y 为一个数如: y = n + 1 那么 1 n 1 n + 1 = 1 ( n + 1 ) n z = n ( n + 1 )
此题可有多组解。解法类似。

代码

#include <iostream>
using namespace std;
int main()
{
    int n;
    cin>>n;
    if (n == 1)
     cout<<-1<<endl;
    else
        cout<<n<<" "<<n + 1<< " "<<n*(n+1)<< endl
}

H

题意

给出一颗树,我们假定树的根为1。现在需要找出两颗互相无交叉的子树,使得他们的权值和最大。

分析

一开始看到这个题时特别的莽,两颗子树的话我只要保证我跟下有两颗以上的子树,然后从中取权值最大的两个,所得到的就是最优的。结果发现原来树上节点的权值可以为负的,就很头疼了。
但是由于树是递归的,所以我们在求解树上的问题时,也可以用递归的方式求解。当前子树我们可以求出一棵最大的子树和当前子树的最大权值,即对于以 u 为根节点的子树,分为包含u的子树权值之和以及不包含 u 的, u 的一棵最大权值的子树和。分别维护后更新 a n s 。可以理解为,是通过dfs递归求解的方式,在树上进行更新操作,最后求得最优解

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+5;
typedef long long LL;
const LL INF = 1LL<<60;
struct node
{
    int v, nxt;
}edge[maxn<<1];
LL dp[maxn], sum[maxn], w[maxn], head[maxn], tot, ans;

void add(int u, int v) {
    edge[tot].v = v; edge[tot].nxt = head[u]; head[u] = tot++;
}

void dfs(int u, int fa) {
    sum[u] = w[u];
    for(int i = head[u]; ~i; i = edge[i].nxt) {
        int v = edge[i].v;
        if(v == fa) continue;
        dfs(v, u);
        sum[u] += sum[v];
        if(dp[u] > -INF) ans = max(ans, dp[u] + dp[v]);
        dp[u] = max(dp[u], dp[v]);
    }
    dp[u] = max(dp[u], sum[u]);
}

int main()
{
    int n;
    scanf("%d", &n);
    memset(head, -1, sizeof(head));
    for(int i = 1; i <= n; i++)
        scanf("%I64d", &w[i]);
    for(int i = 1; i < n; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v); add(v, u);
    }
    ans = -INF;
    for(int i = 1; i <= n; i++) dp[i] = -INF;
    dfs(1, -1);
    if(ans <= -INF) puts("Impossible");
    else printf("%I64d\n", ans);
    return 0;
}

特别感谢由Muvsea提供的部分思路与代码

猜你喜欢

转载自blog.csdn.net/ShadowGhostH/article/details/81212011
今日推荐