文章目录
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)
p1、p2、p3⋅⋅⋅px(pi小于N),每个质数可以选择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} ai−ai−1,易得对于差分数组求前缀和即可得到数组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+bxn−1+……+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=1n∑j=1mai⊕bi
答案对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;
}