初二国庆集训·国庆测试1
2020.1.5 UPDATE
Markdown 炸了,我忏悔。
前言
这场考试真的把我打自闭了。
先是 T2 调了半个小时一直过不了样例,再后面三题都做完了 T1 却毫无思路。。。
总体不是特别难。
正文
T1 数列问题
题面
题目描述
小明和小红在一起玩游戏,小明觉得现在玩的游戏太无聊了,便给小红出了一道数列问题。数列问题的题目如下:
一开始,小明给小红一个只含有数字1的数列,接下来,小红可以对这个数列进行以下操作中的一种操作:
- 对序列中已有的一个元素加1.
- 复制一个序列中已有的元素到序列的末尾.
比如:一开始序列中只有一个元素
[1]
。我们可以选择这个序列中的第一个元素进行第二种操作,即复制操作,这样,这个序列就变成了
[1, 1]
。然后,我们可以对这个序列中的第一个元素进行第一种操作,即将第一个元素加一。这样,这个序列就变成了
[2, 1]
。现在,小明问小红,她最少需要进行多少次操作,可以使得序列中所有元素的和至少为 \(n\)。
小红面对小明的这个问题一脸懵逼,于是,她来求助聪明的你,希望你能够帮助她解决这个问题。
输入格式
第一行一个数字 \(t(1\le t\le1000)\),表示测试数据的组数。
接下来 \(t\) 行,每行一个数字 \(n(1\le n\le10^9)\),表示序列所有元素和至少应该达到的数。
输出格式
对于每一个问题,回答一个数字,表示小红最少需要操作的次数。
样例
样例输入
5
1
5
42
1337
1000000000
样例输出
0
3
11
72
63244
解析
企图硬算:没打出来,失败。
企图爆搜:程序爆炸,失败。
企图骗分:过了样例并且 AC。
尝试输出 \(\sqrt n\)。
样例输入: \(n\) | \(\sqrt n\) | 样例输出 |
---|---|---|
1 | 1.000000 | 0 |
5 | 2.236068 | 3 |
42 | 6.480741 | 11 |
1337 | 36.565011 | 72 |
1000000000 | 31622.776602 | 63244 |
有什么规律?
- 如果第二列的小数部分 \(\ge0.5\)
第三列的值为第二列的值向下取整再乘以 2 - 否则,如果第二列的小数部分不为 \(0\)
第三列的值为第二列的值向下取整,乘以 2 再减 1 - 否则
第三列的为第二列的值向下取整,乘以 2 再减 2
#include<cmath>
#include<cstdio>
int t,n,mn;
void ly_AK_IOI(int x){ //不要管这个函数名
double y=sqrt(x);
int z=(int)y;
if(y-z>=0.5)
printf("%d",z<<1);
else if(y-z==0)
printf("%d",(z<<1)-2);
else printf("%d",(z<<1)-1);
return;
}
int main(){
scanf("%d",&t);
while(t--){
scanf("%d",&n);
ly_AK_IOI(n);
if(t)puts("");
}
return 0;
}
T2 Teamwork【DP】
题面
解析
这。。好像。。可能。。大概。。应该。。也许。。或许。。大约。。(以下省略1万字)是道DP吧?
【凭感觉做题晚期患者】
用 \(f[i]\) 表示让第 \(i\) 头奶牛为当前分组的最后一头,已分好的所有组的奶牛技能值之和。
#include<cstdio>
int n,k,mx;
int a[10005],f[10005];
int max(int x,int y){
return x>y?x:y;
}
int min(int x,int y){
return x<y?x:y;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
for(int i=1;i<=n;++i){
mx=0;
for(int j=i;j<min(i+k,n+1);++j){ //枚举第i~i+k-1头牛为当前分组末尾的情况
mx=max(mx,a[j]); //求当前分组的最大值,因为每次循环会多一头牛,所以每次都要重新求
f[j]=max(f[j],(j-i+1)*mx+f[i-1]); //不用在程序末尾重新把技能值相加的好方法
}
}
printf("%d",f[n]);
return 0;
}
T3 飞行路线【DP(?)】【最短路】
题面
解析
听他们说,这是一道分层图,可惜我不会
所以,我们可以先动动我们不太灵光的脑子,先打一个 \(Dijkstra\) 模板!
好像是因为还没有彻底从上一道DP中清醒过来吧,所以,我突然发现!!
我可以用DP来做这道题鸭!
好像也不是正宗DP呢,就是个类似的东西啦~
枚举从0条免费航线到 \(k\) 条免费航线时的最短路,也就是跑 \(k+1\) 次最短路就行啦~
这数据范围应该过的了叭~
那么怎么实现这个“DP最短路”呢?
简单,把 \(Dijkstra\) 里的 \(dis\) 数组改一下,用 \(dis[i][j]\) 表示从起点出发,有 \(i\) 条免费航线时到 \(j\) 的最短路就行啦~
#include<queue>
#include<cstdio>
#include<vector>
using namespace std;
const int maxn=1e4+5;
struct WJC_AK_IOI{ //同样不要注意这个结构体名
int e,w; //终点、权值
WJC_AK_IOI(int E=0,int W=0){
e=E,w=W;
}
};
int dis[15][maxn];
bool vis[15][maxn];
int n,m,k,x,y,z,s,t;
vector<WJC_AK_IOI>g[maxn];
priority_queue<pair<int,int> >q;
void add(int b,int r,int d){
g[b].push_back(WJC_AK_IOI(r,d));
g[r].push_back(WJC_AK_IOI(b,d));
return;
}
void Dijkstra(void){ //老师对于我在括号里写void及其不满,但我觉得这样更整齐啊QWQ
for(int i=0;i<=k;++i){ //枚举免费航线数量
q.push(make_pair(0,s));
dis[i][s]=0;
while(!q.empty()){
int f=q.top().second;
q.pop();
if(vis[i][f])continue;
vis[i][f]=1;
for(int j=0;j<g[f].size();++j){
int e=g[f][j].e,w=g[f][j].w;
if(dis[i][e]>dis[i][f]+w){
dis[i][e]=dis[i][f]+w;
q.push(make_pair(-dis[i][e],e)); //以上为Dijkstra板子
}
if(i){ //防RE
if(dis[i][e]>dis[i-1][f]){
dis[i][e]=dis[i-1][f]; //第一维只用到i和i-1,貌似可以用滚动数组优化
q.push(make_pair(-dis[i][e],e)); //但我懒得写,能过就行QWQ
}
}
}
}
}
return;
}
void read(int&x){
x=0;
char ch=getchar();
while(ch<'0'||ch>'9')
ch=getchar();
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch&15);
ch=getchar();
}
return;
}
int main(){
read(n);read(m);read(k);read(s);read(t);
for(int i=0;i<=10;++i){
for(int j=0;j<n;++j) //坑点:编号从0到n-1,但是貌似用memset就不用在意这些QWQ
dis[i][j]=0x3f3f3f3f;
}
while(m--){
read(x);read(y);read(z);
add(x,y,z);
}
Dijkstra();
printf("%d",dis[k][t]);
return 0;
}
T4 公路修建问题【最小生成树】
题面
解析
考试的时候只让输出花费最大的公路的花费估计是老师懒得写SPJ,所以我就不写路径啦~
总体来说,就是两次最小生成树。
为了方便先把 \(m--\)。
第一次从 \(1\) 到 \(m\) 枚举一级公路。
- Q:一级公路花费不是更大吗?为什么不是 \(1\) 到 \(k\) 呢?
- A:万一前 \(k\) 条边有环,也就是 \(find(begin)==find(end)\) ,\(continue\) 了呢?
(80分死在这儿了QAQ)
我们用变量 \(cnt\) 统计加入最小生成树的边数,当 \(cnt==k\) 时,\(break\)。
第二次从 \(1\) 到 \(m\) 循环枚举二级公路。
#include<cstdio>
#include<algorithm>
using std::sort;
const int maxn=2e4+5;
int f[maxn];
int n,k,m,ans,cnt;
struct yzk_AK_IOI{ //同样不要在意这个结构体名
int b,e,c; //起点、终点、权值
yzk_AK_IOI(int B=0,int E=0,int C=0){
b=B,e=E,c=C;
}
bool operator<(const yzk_AK_IOI q)const{
return c<q.c;
}
}a[maxn],b[maxn]; //A、B分别存一级公路、二级公路
int max(int x,int y){
return x>y?x:y;
}
void init(){ //初始化函数,因为没有调用而查了20 mins错。。。
for(int i=1;i<=n;++i)
f[i]=i;
return;
}
int find(int x){
return x==f[x]?x:f[x]=find(f[x]);
}
void bing(int x,int y){
f[x]=y;
return;
}
void read(int&x){
x=0;
char ch=getchar();
while(ch<'0'||ch>'9')
ch=getchar();
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch&15);
ch=getchar();
}
return;
}
int main(){
read(n);read(k);read(m);
m--;
init();
for(int i=1;i<=m;++i){
read(a[i].b);read(a[i].e);
b[i].b=a[i].b;b[i].e=a[i].e;
read(a[i].c);read(b[i].c); //A、B的起点相同,权值不同,个人认为两个数组比较好理解
}
sort(a+1,a+m+1);
sort(b+1,b+m+1);
for(int i=1;i<=m;++i){
if(cnt==k)break;
int fx=find(a[i].b);
int fy=find(a[i].e);
if(fx==fy)continue; //之所以i不能从1到k就是因为这里可能被continue掉
bing(fx,fy);
ans=max(ans,a[i].c); //统计最大花费
cnt++; //统计已加入的边数,与AK差的20分在这儿QWQ
}
//最小生成树模板
for(int i=1;i<=m;++i){
int fx=find(b[i].b);
int fy=find(b[i].e);
if(fx==fy)continue;
bing(fx,fy);
ans=max(ans,b[i].c); //统计最大花费
}
printf("%d",ans);
return 0;
}
\(*^-^*)/ 写完啦!!!
咦,怎么还没下课?不如。。。
再求个关注?
(*^▽^)ツ▄ ←点赞、关注、收藏
—— · EOF · ——
真的什么也不剩啦 😖