B题
题目链接如下:
样例输入
5 1 200000 123
输出
4
思路如下:
最小生成树问题(MST),对kruskal进行变形,但由于这题范围太大,不能暴力枚举所有任意两个数的最大公约数,所以要看到这道题目的特殊性。因为我们看到了边权是gcd即最大公约数,考虑到素数分布:
当n很大时(差不多大于1000时),就肯定存在素数,边权就都是1,得出的最小生成树值就是n - 1。在n小于1000时正常跑kruskal,另外还有一种情况,就是L = R时,说明边权都是L,得出的最小生成树就是(n - 1) * L,并且这里注意开long long,不然还会有用例过不了。
代码如下:
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long LL;
const int N = 100010;
int idx;//这里用来记录边数。
int p[N];
struct Edge
{
int a, b, w;
bool operator < (const Edge& t) const
{
return w < t.w;
}
} edge[N];
int n,L,R,a[200001];
int gc[100][100];
LL seed;
LL xorshift64()
{
LL x=seed;
x^= x<<13;
x^= x>>7;
x^= x<<17;
return seed=x;
}
int gen()
{
return xorshift64() % (R-L+1)+L;
}
int gcd(int a,int b)
{
if(b>a)
{
int c=a;
a=b;
b=c;
}
int yu=1;
while(yu!=0)
{
yu=a%b;
a=b;
b=yu;
}
return a;
}
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
LL kruskal()
{
sort(edge + 1, edge + 1 + idx);
LL res = 0, cnt = 0;
for (int i = 1; i <= idx; i++)
{
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
a = find(a), b = find(b);
if (a != b)
{
p[a] = b;
res += w;
cnt++;
}
if (cnt == n - 1) break;
}
return res;
}
int main()
{
scanf("%d%d%d%llu",&n,&L,&R,&seed);
for(int i=1; i<=n; i++)
{
a[i]=gen();
}
if(L==R)
{
cout<<(LL) (n-1)*L<<endl;
return 0;
}
if(n>=1000)
{
cout<<n-1<<endl;
return 0;
}
for(int i=1; i<=n; i++)
{
p[i]=i;
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
{
int w = gcd(a[i], a[j]);
edge[idx ++ ] = {i, j, w};
}
cout<<kruskal()<<endl;
return 0;
}
C题
题目链接如下:
样例输入
5
输出
3
1 2
1 3
思路如下:
这道题我存在很大的问题,主要就是看好几遍题目后不知道这道题究竟想让输出什么,后来经过朋友提醒和自己思考才算是明白了。
结合前面的题目描述,大概就是这棵树如果根节点为黑那么其所有子结点为黑。如果其根节点为白的话,则其子结点没有限制。
考虑完全二叉树:得出规律,f=l*r+1,f表示数目,l表示左子树数目,r表示右子树数目
结点数目 | 种类数目 |
---|---|
2 | 3 |
3 | 5 |
4 | 7 |
5 | 11 |
6 | 16 |
7 | 26 |
代码如下:
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int main()
{
long long k;
long long a[20000]={0};
long long b[20000]={0};
int i=1;
cin>>k;
if(k==2)
cout<<"1"<<endl;
else
{
long long n=k-1;
long long c=1;
long long d=2;
for(i=1;n>0;i++)
{
if(n==2)
{
a[i]=c;
b[i]=d;
cout<<d<<endl;
break;
}
if(!(n&1))
{
a[i]=c;
b[i]=d;
d++;
n=n/2;
}
else
{
a[i]=c;
b[i]=d;
c=d;
d++;
n--;
}
}
for(int j=1;j<=i;j++)
{
cout<<a[j]<<" "<<b[j]<<endl;
}
}
return 0;
}
D题
题目链接如下:
样例输入
4
1 2
3 2
2 1
4 1
输出
4 4
8 6
8 8
10 8
思路如下:
本题的思路就是将两个方向分开算,因为开一个二维数组明显是不太现实的,因为规模太大了,而且处理起来时间肯定也是会超限的。
所以我们直接开两个数组,一个表示x轴方向上的情况,另一个表示y轴方向上的情况,我们以x轴方向为例说明本题的规律。
如下图右上角部分所示:
每一列出现一个元素就在该列上+1,而该列的周长和总共的周长分别满足下面关系。
设该列上有i个元素,则在该列上的周长为d=2
*i+2,考虑到左右之间有重叠的,我们可以每加一个处理一次,每加一次该列上的周长就加上二,然后将重叠的去掉。
代码如下:
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int a[200005]= {0};
int b[200005]= {0};
int main()
{
int n;
scanf("%d",&n);
int x,y;
int sumx=0,sumy=0;
for(int i=0; i<n; i++)
{
cin>>x>>y;
a[x]++;
b[y]++;
if(a[x]==1)
sumx+=4;
else
sumx+=2;
if(a[x]<=a[x+1])
sumx-=2;
if(a[x]<=a[x-1])
sumx-=2;
if(b[y]==1)
sumy+=4;
else
sumy+=2;
if(b[y]<=b[y+1])
sumy-=2;
if(b[y]<=b[y-1])
sumy-=2;
cout<<sumx<<" "<<sumy<<endl;
}
return 0;
}
G题
题目链接如下:
样例输入
3 10
94 100 99
输出
97.6666666666
思路如下:
本题的思路很简单,就是给定一个数n,表示输入数的个数,给定一个数k,题目要求的是求平均数,该数保留小数点后k位。很明显k最大达到了100000,那一定不能直接算。很多人是用字符串来模拟的,我这个思路省去了这个步骤:
先把整数部分求出来,点上小数点,再一遍遍循环求小数部分的每一位,循环k次即可。
代码如下:
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n,k;
scanf("%d%d",&n,&k);
int sum=0;
int zan;
for(int i=0;i<n;i++)
{
scanf("%d",&zan);
sum+=zan;
}
int chu=sum/n;
int yu=sum%n;
cout<<chu<<".";
for(int i=0;i<k;i++)
{
yu*=10;
chu=yu/n;
yu=yu%n;
cout<<chu;
}
return 0;
}
H题
题目链接如下:
样例输入1
2 66 22
1 23 2
66 8 90
输出
2
样例输入2
4 16 22
1 23 11
5 8 14
2 36 99
15 22 27
输出
27
思路如下:
题目大意是Yuna去打怪,每个怪有生命值和能量值以及价值,打掉这个怪后Yuna获得其价值,但自身生命值和能量值减少同样的点,能量值不够的话减少相应的生命值,生命值小于等于零时Yuna死亡。保证其不死且获得最大价值。
这是一道典型的二维01背包问题,简单说一下就是创建一个二维数组,用元素dp[i][j]来表示有i点生命值,j点能量值的最优解。
其中的核心部分就是我们要将Yuna能量值减小到小于零的情况进行讨论。描述在注释部分。
代码如下:
#include <bits/stdc++.h>
using namespace std;
long long n,H,S,h[1212],s[1212],w[1212],dp[301][301];
int main()
{
cin >>n>>H>>S;
for(int i=1; i<=n; i++)
cin >>h[i]>>s[i]>>w[i];
for(int i=1; i<=n; i++)
{
for(int j=H; j>=0; j--)
{
for(int k=S; k>=0; k--)
{
if(k>=s[i]&&j>=h[i])//这是生命值和能量值都大于零的情况,即一般情况。
dp[j][k]=max(dp[j][k],dp[j-h[i]][k-s[i]]+w[i]);
else if(k<s[i]&&j+k>=h[i]+s[i])//这就是能量值降为零之后的情况,注意更新时是在dp[j][0]处更新的。
dp[j][k]=max(dp[j][k],dp[j+k-h[i]-s[i]][0]+w[i]);
else
dp[j][k]=dp[j][k];//这就是不更新的情况,直接使用本身的值。
}
}
}
cout <<dp[H-1][S];
return 0;
}
/*
2 66 22
1 23 2
66 8 90
4 16 22
1 23 11
5 8 14
2 36 99
15 22 27
*/
M题
题目链接如下:
样例输入
5 5
00000
00100
01010
01100
00000
输出
00000
00100
01110
01100
00000
00000
01110
01010
01110
00000
思路如下:
题目大意是给定一个n行m列的矩阵,只由0或1构成,且边框都为0,要找到两个相同规模的矩阵,只有这两个矩阵相应位置都为1时,原矩阵相应位置才为1,寻找这两个矩阵。(答案不唯一)
这里采用的思路是,A最左边一列是 1, B最右边一列是 1,然后行分奇偶全染成 1。
代码如下:
#include <bits/stdc++.h>
using namespace std;
char a[501][501]={0};
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
}
}
for(int i=1;i<=n;i++)
{
if(i&1)
{
cout<<'1';
for(int j=2;j<m;j++)
{
cout<<a[i][j];
}
cout<<'0'<<endl;
}
else
{
for(int j=1;j<m;j++)
{
cout<<'1';
}
cout<<'0'<<endl;
}
}
for(int i=1;i<=n;i++)
{
if(!(i&1))
{
cout<<'0';
for(int j=2;j<m;j++)
{
cout<<a[i][j];
}
cout<<'1'<<endl;
}
else
{
cout<<'0';
for(int j=2;j<=m;j++)
{
cout<<'1';
}
cout<<endl;
}
}
return 0;
}
本次比赛总结
我的感觉就是太菜了,看着周围的同学都过了一道又一道题目,而我在比赛中只能干着急,有点难受。相关的知识点还是掌握的不够牢靠,之后还是需要多多练习,很多类型题还是需要多看。