导语
第一次训练赛,根据队内安排,选择值得参考的题目进行整理
涉及的知识点
整数除法、01背包、思维、数据量&最小生成树、树、矩阵
题目:签到题G,简单题DH,中等题BCM
链接:复现
题目
G
题目大意:给出n个整数和一个整数k,计算这n个整数的平均值,保留k位
思路:整数除法,注意取余
代码
#include <bits/stdc++.h>
using namespace std;
int N,K,T,sum,le;
int main() {
string s;
scanf("%d%d",&N,&K);
for(int i=0; i<N; i++) {
int t;
scanf("%d",&t);
sum+=t;
}
T=sum/N;//获取整数
le=sum%N;//获取余数,用来获得小数部分
printf("%d.",T);
while(K--)
{
le*=10;
printf("%d",le/N);
le=le%N;
}
return 0;
}
D
题目大意:二维空间里放了n个盒子,有水平往左和竖直往下两种重力,求每次重力作用之后形成的轮廓周长
思路:模拟过程即可,每次放一个判断是否添加或减少边数
代码
#include <bits/stdc++.h>
using namespace std;
int n,numx[200001],numy[200001],ansx,ansy;
int main() {
scanf("%d",&n);
while(n--) {
int x,y;
scanf("%d%d",&x,&y);
if(numx[x-1]<=numx[x])//当前列大于等于左边列
ansx++;//左边可加一
else
ansx--;//重合,左边减一
if(numx[x+1]<=numx[x])//当前列大于等于右边列
ansx++;//右边可加一
else
ansx--;//重合,右边减一
if(numx[x]==0)//底部无方块,下部加一
ansx++;
else
ansx--;//重合,下部减一
ansx++,numx[x]++;//上部加一,该列高度加一
if(numy[y-1]<=numy[y])
ansy++;
else
ansy--;
if(numy[y+1]<=numy[y])
ansy++;
else
ansy--;
if(numy[y]==0)
ansy++;
else
ansy--;
ansy++,numy[y]++;
printf("%d %d\n",ansx,ansy);
}
return 0;
}
H
题目大意:每个怪会扣指定的血量和防御,并有各自价值,给出初始防御与血量,防御为0时可以用血量来抵,血量不能为0,求获得的最大价值
思路:有约束的01背包,将条件分开来看即可,最后输出血量为1时的最优解
代码
#include <bits/stdc++.h>
using namespace std;
long long n,H,S,h[1212],s[1212],w[1212],dp[301][301];
int main() {
ios::sync_with_stdio(0),cin.tie(0);
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][k]=max(dp[j][k],dp[j+k-h[i]-s[i]][0]+w[i]);
cout <<dp[H-1][S];
return 0;
}
B
题目大意:给定一个n个点的无向完全图,每个点有自己对应的权重a[t],i和j之间的边的权重是gcd(a[i],a[j]),保证a[t]随机,给出a[t]产生的范围,求最小生成树
思路:当n很大的时候(且数值取值范围不为单值),存在一个质数或者一个与其他数都互质的质数,所以答案为n-1(即1*(n-1)),当n很小的时候,当做最小生成树的模板题即可,当取值范围为单值,只需要取n-1个单值即可,开long long
粗略证明:当n很大的时候,如果R-L很小,说明数据密集,n≥R-L,数基本上都是连续+重复的,存在一个跟其他数都互质的数,R-L很大的时候,数据很分散,也存在这样一个数,int范围内两个素数间最小间距不到几百
通过模拟,在题目给定数据范围内未能推翻结论
代码
#include <bits/stdc++.h>
using namespace std;
unsigned long long n,L,R,a[200001],fa[12121];
unsigned long long seed,ans;
unsigned long long xorshift64() {
unsigned long long 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) {
int res=0;
while(b) {
res=b;
b=a%b;
a=res;
}
return a;
}
typedef struct node {
int x,y,value;
node(int _x,int _y,int _v) {
x=_x;
y=_y;
value=_v;
}
bool operator<(node a)const {
return value>a.value;
}
} node;
int Seek(int x) {//路径压缩
if(x==fa[x])
return x;
return fa[x]=Seek(fa[x]);
}
bool Union(int x,int y) {//合并
int _x=Seek(x),_y=Seek(y);
if(_x==_y)
return 0;
fa[_x]=fa[_y];
return 1;
}
priority_queue<node>Q;
int main() {
scanf("%llu%llu%llu%llu",&n,&L,&R,&seed);//L~R是所获得的数的范围
for(int i=1; i<=n; i++)
a[i]=gen();
if(R==L) {//n个数都相同
printf("%llu",L*(n-1));
return 0;
}
else if(n>5)//n较大,可以试出来边界值
{
printf("%llu",n-1);
return 0;
}
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++)
if(i!=j)
Q.push(node(i,j,gcd(a[i],a[j])));
fa[i]=i;
}//录入边
while(!Q.empty()) {//构造最小生成树
node t=Q.top();
Q.pop();
if(!Union(t.x,t.y))
continue;
ans+=t.value;
}
printf("%llu",ans);
return 0;
}
C
题目大意:构造一棵树,这棵树满足这样的条件:如果一个点为黑色,其子树都为黑色,如果为白色,则不为然,给出一个数K( 2 ≤ 2\le 2≤K ≤ 2 × 1 0 8 \le2×10^8 ≤2×108),求一棵能够满足有K个染色方案的树,并将其存在的边输出
思路:设函数
f
(
x
)
f(x)
f(x)表示子树黑白染色的方案数(编号为x的点为白色,x为黑色的时候方案数为1),可以得到转移方程:
f
(
x
)
=
1
+
∏
(
f
(
y
)
+
1
)
f(x)=1+\prod(f(y)+1)
f(x)=1+∏(f(y)+1)。
对于这个方程,x和y都为白,第一个1代表所有子树(即y)都为黑的方案,第二个1代表所有y各自的子树都为黑的方案。累乘所有子树的构造方案。
选择策略:由上到下构造这棵树,对于剩下的方案数K,如果K为偶数,在这一层添加一个与父节点相连接的点,可以很容易知道,此时染色方案乘2,等价于K除以2,当K为奇数,下接一层,此时K-1(下接一层,即向下加一个点,这个点之后增加一个染色为白的方案,不可能能加一个染色为黑,因为当该点为黑时,其父节点已经先一步染成黑了),具体构造如图:
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
long long int n;
vector<long long int>v1;
vector<long long int>v2;
scanf("%lld",&n);
if(n==2)//特判
printf("%d",1);
else {
n--;
long long int count1 = 1;//当前层所有点的父节点
long long int count2 = 2;//当前层的第一个待连接的点,也是当前已经用过的点的数目+1
while(n) {
v1.push_back(count1);
v2.push_back(count2);//将解存入
if(n==2) {
printf("%lld\n",count2);//当剩余的构造量为2时
//只需要再连接一个点就能构造完成,此时的count2便是所用的点的数目
break;
}
if(n%2==0) {//本层加一个点
count2++;
n>>=1;
} else {
count1=count2++;//下接一层
n--;
}
}
int len=v1.size();
for(int i=0; i<len; i++)
printf("%lld %lld\n",v1[i],v2[i]);
}
return 0;
}
递归解法
#include<cstdio>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=1e5+100;
int n,line,ver=1,tot;
pair<int,int> st[maxn];
void create(int x,int par) {
if(x==2)
return;
if(x==3) {
st[tot++]= {par,++ver};
create(x-1,ver);
} else if(x&1) {
st[tot++]= {par,++ver};
st[tot++]= {par,++ver};
create((x-1)/2,ver);
} else {
st[tot++]= {par,++ver};
create(x-1,ver);
}
}
signed main() {
scanf("%lld",&n);
create(n,1);
printf("%lld\n",ver);
for(int i=0; i<tot; i++)
printf("%lld %lld\n",st[i].first,st[i].second);
}
M
题目大意:给出一个只有0/1的矩阵C,将这个矩阵经过变化得到两个矩阵A、B,要求如果A、B的大小规模与C相同,C对应位置为1,则A、B对应位置也都为1,C对应位置为0,则A、B对应位置的或为1,保证C的边缘都为0
思路:一种简单的策略,A的最左都为1,最右都为0,B的最右都为1,最左都为1,之后按照奇偶相间将A、B的对应行设置为0,1,如A的奇数行(除最右)都为1,偶数行(除最左)都为0,B则相反
代码
#include <bits/stdc++.h>
using namespace std;
int n,m,data[501][501];
int main() {
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
scanf("%1d",&data[i][j]);
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++)
if(j==1)
printf("1");
else if(j==m)
printf("0");
else if(i%2==1)
printf("1");
else
data[i][j]==1?printf("1"):printf("0");
printf("\n");
}
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++)
if(j==1)
printf("0");
else if(j==m)
printf("1");
else if(i%2==0)
printf("1");
else
data[i][j]==1?printf("1"):printf("0");
printf("\n");
}
return 0;
}
总结
本周的测试,列出了6个需要整理的题目,细细看来,其实还是有很多题都是能做的,M题和B题都可以争取一下,也需要注意更正忘记开long long 的缺点,今后需要更多的练习。