东北林业大学第15届校赛(大二组) 题解

A.小林找工作

题目链接:NEFU OJ-2223

题目大意:

小林最近在找工作,但因为小林很努力并且准备的很充分,所以收获了很多的offer,人送外号"offer收割机",但是小林也有他自己的一些烦恼,不知道如何选offer,现在小林手里有n个offer,每个offer都有一个工作地点,假设世界是一条数轴,那么每个工作地点的位置对应一个pi,小林有m个朋友,每个朋友的工作地点对应一个qi,因为小林喜欢离朋友的距离越近越好,那么小林想知道对于每个朋友他该选择哪个offer才能离朋友距离最近,输出最近距离。

对小林的n个工作地点排序后二分查找:
对于每一个朋友的家q[i],用lower_bound找到第一个大于它的值p[x],则与其最近的地点要不是p[x],要不是p[x-1]。但是要特判一下当q[i]小于p[1](下标从1开始的话)时以及q[i]大于p[n]时的情况。

代码

#include <cstdio>
#include <algorithm>

using namespace std;

int p[100005];

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
       scanf("%d",&p[i]);
    sort(p+1,p+n+1);
    for(int i=0;i<m;i++){
      int temp;
      scanf("%d",&temp);
      int pos=lower_bound(p+1,p+n+1,temp)-p;
      if(pos>n) pos=n;
      int ans=abs(temp-p[pos]);
      if(pos!=1) ans=min(ans,abs(temp-p[pos-1]));
      printf("%d\n",ans);
    }
    return 0;
}

B.xx的树

题目链接:NEFU OJ-2225

题目大意

众所周知,xx有一个神奇的能力,他可以修改树上节点的权值。具体操作如下:xx可以选中i号节点,则i号节点和它的子树所有节点权值全部加x。经过m此操作后,xx想知道树上每一个节点的权值是多少。
树上节点编号为1-n,1号节点为根节点。

思路很简单,将每次操作的值记在第i个结点上,然后遍历树的时候给每个子结点加上其父结点的值即可。

这道题m、n的数据范围是 1 0 5 10^5 105,属于点稀疏图,因此应该用邻接表/链式前向星来储存。然后遍历树即可。但是我太菜了,暑假没好好学,不会啊 阿巴阿巴

连夜补上知识漏洞:链式前向星的实现及遍历

dfs遍历代码

#include <cstdio>
#include <iostream>
using namespace std;

const int maxn=2e5+10;

typedef struct{
  int to;
  int next;
}edge;

edge e[maxn];
int cnt=1;
int head[maxn];
int vis[maxn];
long long  value[maxn];

void add(int u,int v){
    e[cnt].to=v;//cnt指向该边
    e[cnt].next=head[u];//该边的next是表头的next,相当于头插法
    head[u]=cnt++;//表头的next等于该边
}

void dfs(int u){
  vis[u]=1;
  for(int i=head[u];i;i=e[i].next){ //i=0时链表遍历完毕
        int v=e[i].to;
        if(vis[v]) continue;
        value[v]+=value[u];
        dfs(v);
  }
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++){
       int u,v;
       scanf("%d%d",&u,&v);
       add(u,v);
       add(v,u);
    }
    for(int i=1;i<=m;i++){
       long long  u,d;
       scanf("%lld%lld",&u,&d);
       value[u]+=d;
    }
    dfs(1);
    for(int i=1;i<=n;i++)
       i==1?printf("%lld",value[i]):printf(" %lld",value[i]);
    return 0;
}

bfs遍历代码

void bfs(int u){
  queue<int>q;
  q.push(u);
  vis[u]=1;
  while(!q.empty()){
       int temp=q.front();
       q.pop();
       for(int i=head[temp];i;i=e[i].next){
           int v=e[i].to;
           if(vis[v]) continue;
           vis[v]=1;
           value[v]+=value[temp];
           q.push(v);
       }
  }
}

vector 实现邻接表写法

#include <cstdio>
#include <string.h>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int maxn=1e5+10;
int N;
vector <int> ver[maxn];
int vis[maxn];
long long  value[maxn];

void add(int u,int v){
     ver[u].push_back(v);
}

void dfs(int u){//起点u
  vis[u]=1;
  for(int i=0;i<ver[u].size();i++){
      int v=ver[u][i];
      if(vis[v]) continue;
      vis[v]=1;
      value[v]+=value[u];
      dfs(v);
  }
}

int main()
{
  int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++){
       int u,v;
       scanf("%d%d",&u,&v);
       add(u,v);
       add(v,u);
    }
    for(int i=1;i<=m;i++){
       long long  u,d;
       scanf("%lld%lld",&u,&d);
       value[u]+=d;
    }
    dfs(1);
    for(int i=1;i<=n;i++)
       i==1?printf("%lld",value[i]):printf(" %lld",value[i]);
	return 0;
}

C.xx玩游戏

题目链接:NEFU OJ-2226

题目大意

xx和yy都想成为最强的人,这次他们决定用游戏决出胜负。
他们找到了一个金字塔形的棋盘,由n行组成,第i行有i个格。
棋盘上有一枚棋子,xx和yy轮流移动它,xx先移动。
现规定有三种移动方式,假设棋子此时位置为(x,y),则可移动到(x+1,y)或(x,y+1)或(x+1,y+1),注意棋子始终不能脱离棋盘。
当一名玩家不能再移动棋子时,即棋子处于(n,n)时,则判负。
现在xx特别想打败yy成为最强的人,所以由他决定棋子的初始位置。
现在已知棋盘大小n,xx和yy都选择最佳策略,xx想知道有多少种棋子初始点的选择方案,可以使自己一定获得胜利。

在这里插入图片描述
博弈论的基本概念:

必败态即不管怎么做都会输的局面,必胜态则是一定会赢的局面。因为博弈中双方都会采取最优策略,所以一个局面要不就是必胜态,要不就是必败态。

①如果一个局面所有后继局面都是必胜态,则该局面为必败态。

②如果一个局面的后继局面中有一个必败态,则该局面为必胜态。

在这里插入图片描述
我列举了一下n=5时的情况,打叉的为必败态,打勾的为必胜态。从右下角开始推,然后每一列都从下往上推即可推出

很容易就能找到规律,偶数列没有必败态,奇数列有(n+1)/2个必败态

代码

#include <cstdio>
#include <algorithm>
#include <map>
#include <iostream>
#include <cstring>
using namespace std;



int main()
{
    int T;
    scanf("%d",&T);
    while(T--){
      long long  n;
      scanf("%lld",&n);
      long long t=(n+1)/2;
      long long bai=(1+t)*t/2;//必败的格子数
      long long sum=(1+n)*n/2;//棋盘总格子数
      printf("%lld\n",sum-bai);
    }
    return 0;
}

D.xx的零食店

题目链接:NEFU OJ-2231

题目大意

xx零食店开张啦,店内共有n种零食,单价分别为1,2,…,n。
为了吸引人们前来购物,xx想出了一个方案
具体规则为:当顾客恰好买了n元的零食后,可以获得一个小礼品,小礼品的种类由k决定(k为所购买的所有零食单价的最小公倍数,不同的k对应不同的种类)。
gg作为xx的好朋友,也来捧场,看到规则后,说xx需要准备的礼品种类太多了。
xx开始慌了,他想知道自己需要准备多少种礼品,你能告诉他吗

大致意思:设数n有k种划分方式,求这k种划分方式共有多少种最小公倍数?

比如3有三种划分方式:{3},{1,2},{1,1,1},其最小公倍数分别为3,2,1,故答案为3

我是用母函数的做法解决的(关于母函数的知识可见普通型母函数

可以发现,选择一个数字1次或多次对最小公倍数的贡献是一样的,因为lcm(a,a,a,……,a)=a。例:2、2、3的最小公倍数和2、3的最小公倍数是相同的。

又考虑到1对于最小公倍数同样是没有贡献的,而且1对于求和有着填充的作用,所以可以从数字2开始考虑将问题转化为:

从2~N中选择k个数,这k个数的和只要小于等于N(剩下的部分由1填满)就代表一种方案(选择0个数就代表N个1的方案)。

比如N=8的时候,我选择1个2和1个3,则相当于得到了一种方案:1、2、2、3或者1、1、1、2、3(反正最小公倍数是一样的,所以当作了一种方案)

但是会发现,选择1个6和选择2、3得到的最小公倍数是一样的,选择10、2和选择5、2得到的最小公倍数也是一样的。因此其实数字6、10是完全不需要考虑的。任何包含6的方案都可以用一个2和一个3来进行代替(2+3<6且最小公倍数相同)。也就是只需要考虑质因子即可。

问题出在4,16,32,9,27等形如 p n p^n pn的数,因为它们只有一个质因子,且不能被质因子替代(2个2的最小公倍数是2而不是4)。

归纳可得到只要考虑质数及质数的幂次就不会出现重复的答案。之所以要考虑质数的幂次是因为发现4、8、16除了1和其本身只有一个因子2,但是却无法用2代替

因此现在问题转化为了:
有x个质数 p 1 、 p 2 、 p 3 ⋅ ⋅ ⋅ p x ( p i 小 于 N ) p_1、p_2、p_3\cdot\cdot\cdot p_x(p_i小于N) p1p2p3pxpiN,每个质数可以选择num[i]次,求有多少种和小于等于N的组合方式?

注:选择num[i]次,代表 p n u m [ i ] p^{num[i]} pnum[i]

多重集的组合问题,利用母函数即可解决,多项式相乘后取指数0~N次的系数之和即可

构建母函数如下:

( 1 + x 2 + x 4 + x 8 + x 16 + ⋅ ⋅ ⋅ ) ⋅ ( 1 + x 3 + x 9 + x 27 + ⋅ ⋅ ⋅ ) ⋅ ⋅ ⋅ ( 1 + x N ) (1+x^2+x^4+x^8+x^{16}+\cdot\cdot\cdot)\cdot(1+x^3+x^9+x^{27}+\cdot\cdot\cdot)\cdot\cdot\cdot(1+x^N) (1+x2+x4+x8+x16+)(1+x3+x9+x27+)(1+xN)

代码

#include <cstdio>
#include <string.h>
#include <iostream>
#include <map>
#include <algorithm>
using namespace std;

int Prime[1005];
bool Isprime[1005];
int cnt=0;

void excel(){//素数筛
    for(int i=2;i<=1000;i++)  Isprime[i]=1;
    for(int i=2;i<=1000;i++){
        if(Isprime[i])
            Prime[cnt++]=i;
        for(int j=0;j<cnt&&Prime[j]*i<=1000;j++){
            Isprime[Prime[j]*i]=0;
            if(i%Prime[j]==0)
                 break;
        }
    }
}

int pow(int base,int power){//求幂
  int ret=1;
  if(power==0)  return 0;//为了后续编写程序的简洁性,所以令x^0为0
  for(int i=1;i<=power;i++)
        ret*=base;
  return ret;
}

long long c[1005];
long long temp[1005];

int main()
{
  excel();
  int N;
  scanf("%d",&N);
  for(int i=0;pow(2,i)<=N;i++)//初始化为第一个多项式,即质数2的多项式
      c[pow(2,i)]++;
  for(int i=1;i<cnt&&Prime[i]<=N;i++){
     memset(temp,0,sizeof(temp));
     for(int j=0;j<=N;j++)
        for(int k=0;j+pow(Prime[i],k)<=N;k++)
            temp[j+pow(Prime[i],k)]+=c[j];
     for(int j=0;j<=N;j++) c[j]=temp[j];
  }
  long long  ans=0;
  for(int i=0;i<=N;i++)
      ans+=c[i];
  printf("%lld\n",ans);
	return 0;
}

E.qyh的签到题

题目链接:NEFU OJ-2224

题目大意

qyh写差分题的时候看见这么一道题,老师给n个人发苹果,从i到n每个人发k个。天哥偷偷加大难度,增加了几种操作来让大家写,操作1是从i开始每个人发1个苹果,操作2是从i开始一直到n每个人分别发1,2,3,4……k个苹果,操作3是从i开始一直到n每个人分别发1,4,9……k^2个苹果,给出人数n和操作次数q,然后输出q次操作后每个人分到的苹果。

差分可以将区间修改转换为单点修改,大大降低了复杂度

用一个数组a来记录每个人的苹果数

差分数组 d i d_i di的值储存的是 a i − a i − 1 a_i-a_{i-1} aiai1,易得对于差分数组求前缀和即可得到数组a。

对于操作1:令 a i a_i ai a n a_n an加1,我们可以将这个信息储存在一个差分数组中,即 d i d_i di加1。

对于操作2:令 a i a_i ai a n a_n an依次加1、2、3……,我们可以将这个信息储存在一个二次差分的数组中,然后再求两次前缀和即可

对于操作3:同理,将信息储存在一个三次差分的数组中,然后再求三次前缀和即可

将三次操作对数组a带来的分别储存在一个差分数组中,然后求前缀和后再加和即可得到答案

在这里插入图片描述
其实不只是 a x 2 + b x + c ax^2+bx+c ax2+bx+c,我手推了一下 x 3 x^3 x3显然也是可以用一个四次差分来存下的,因此差分显然是解决这类问题的通解:即对于一个区间加上形如 a x n + b x n − 1 + … … + c ax^n+bx^{n-1}+……+c axn+bxn1++c,对于 x k x^k xk用一个k+1次差分储存值,最后求和即可。

代码

#include <cstdio>
#include <string.h>
#include <iostream>
#include <map>
#include <algorithm>
using namespace std;

int n,q;
const long long  mod=1e9+7;
const int maxn=1e5+5;
long long  d1[maxn],d2[maxn],d3[maxn];

void pre_sum(long long d[]){//对数组求前缀和
  for(int i=1;i<=n;i++)
     d[i]+=d[i-1],d[i]%=mod;
}

int main()
{
  scanf("%d%d",&n,&q);
  for(int i=0;i<q;i++){
     int type,pos;
     scanf("%d%d",&type,&pos);
     if(type==1) d1[pos]++;
     if(type==2) d2[pos]++;
     if(type==3) d3[pos]++,d3[pos+1]++;
  }
  pre_sum(d3);
  pre_sum(d3);
  pre_sum(d3);
  pre_sum(d2);
  pre_sum(d2);
  pre_sum(d1);
  for(int i=1;i<=n;i++)
       i==1?printf("%lld",(d1[i]+d2[i]+d3[i])%mod):printf(" %lld",(d1[i]+d2[i]+d3[i])%mod);
	return 0;
}

G.天哥的序列

题目链接:NEFU OJ-2232

题目大意

天哥喜欢有规律的东西,比如1~ n的序列。现在给你一个长度为n的序列,你可以对其中的数加k或者k的正整数倍,也可以不操作,请问是否能得到1~ n的序列(只要序列里有1~n就可以)。如果能让天哥满意,输出“YES”,如果不能则输出“NO”。

思想很简单,每个数可以不变也可以加上k或者k的正整数倍,就是说每个数可以不变或者向上变化。可以将这道题想象为用k种数字来填满n个位置。

比如数字1可以填到1、1+k、1+2k……这些位置上
因为每个数只能向上变化(或不变),所以从小到大排序后填位置,让每个数填到尽可能小的位置上即可。

如果发现一个数字没有位置可以填了,说明无解

代码

#include <cstdio>
#include <algorithm>
#include <map>
#include <iostream>
#include <cstring>
using namespace std;

const int maxn=1e6+5;

int num[maxn];//系数
int a[maxn];

int main()
{
    int n,k;
    int flag=1;
    memset(num,0,sizeof(num));
    num[0]=1;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    sort(a+1,a+n+1);
    for(int i=1;i<=n;i++){
      if(a[i]>n) {flag=0;break;}
      int j=num[a[i]%k]*k+a[i]%k;
      if(j>n||j<a[i]) {flag=0;break;}
      num[a[i]%k]++;
    }

    if(flag) printf("YES\n");
    else printf("NO\n");
    return 0;
}

H.xx的水题

题目链接:NEFU OJ-2227

题目大意

众所周知,xx是一个善良的人。
xx担心比赛题太难,特意出了一道水题给大家。xx现在给出两个数组a和b,长度分别为n和m,现在xx想要知道
∑ i = 1 n ∑ j = 1 m a i ⊕ b i \sum_{i=1}^n\sum_{j=1}^m a_i⊕b_i i=1nj=1maibi
答案对1000000007取模

计算两个数组,每个数位上0、1的个数即可。
因为异或的性质,0和1异或或者1和0异或才能产生贡献,第i位的贡献即为 2 i 2^i 2i

代码

#include <cstdio>
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;

const long long mod=1000000007;
long long a_0[50];
long long a_1[50];
long long b_0[50];
long long b_1[50];

int  main()
{
    int  n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
      long long temp;
      int cnt=0;
      scanf("%lld",&temp);
      while(temp){
        if(temp%2) a_1[cnt]++;
        else a_0[cnt]++;
        temp/=2;
        cnt++;
      }
      for(int j=cnt;j<=50;j++) a_0[j]++;
    }

    for(int i=1;i<=m;i++){
       long long temp;
       int cnt=0;
       scanf("%lld",&temp);
       while(temp){
         if(temp%2) b_1[cnt]++;
         else b_0[cnt]++;
         temp/=2;
         cnt++;
       }
       for(int j=cnt;j<=50;j++) b_0[j]++;
    }

    long long ans=0;
    long long cnt=1;
    for(long long i=0;i<=50;i++){
       ans+=((a_0[i]*b_1[i]%mod+a_1[i]*b_0[i]%mod)*cnt%mod);
       ans%=mod;
       cnt*=2;
       cnt%=mod;
    }
    printf("%lld\n",ans);
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值