CSP模拟30
难得改完一次题,写篇题解祭一下
A.枫(P7485 「Stoi2031」枫)
考场居然打了个高分暴力
我的思路:假设我们已知最后一个数,逆推,不断往该数前(或后)加了多少数,直至完成这个操作。
荣获 $96pts$ 的好成绩 评测记录
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int M=1e6+10;
int T,q,n,m,wei;
int top,tail;
void dfs(int sum,int sub,int k,int c){
if(sum==1){
return;
}
dfs(sum-sub,(sum-sub-1)/(k+1)+1,k,c+1);
if(c&1){
int pl=1+(top-1)/k;
top+=pl;
tail+=sub-pl;
}
else {
int pl=1+(tail-1)/k;
top+=sub-pl;
tail+=pl;
}
}
int main(){
scanf("%d",&T);
for(int k=1;k<=T;k++){
scanf("%d%d",&q,&m);
for(int i=1;i<=q;i++){
scanf("%d",&n);
if(n==1||n==2){
cout<<n<<' ';
continue;
}
if(k>=n-1){
cout<<n/2+1<<' ';
}
else if(k==n-2||k==n-3){
cout<<(n+1)/2<<' ';
}
else {
top=1,tail=1;
dfs(n,(n-1)/(k+1)+1,k,1);
cout<<top<<' ';
}
}
cout<<'\n';
}
return 0;
}
正解:
一些步骤相似,可不可以认为我敲了正解(bushi)
(fengwu大佬说) 给出了每次捡枫叶的最大枫叶数,显然是递推。
我们求出当前个数第一次“挽回”的个数,求出下一状态的“思念”,插入一定的枫叶即可。(注意顺序)
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int M=1e6+10;
int T,q,n,m;
int ans[M];
int main(){
scanf("%d",&T);
ans[1]=1;
for(int k=1;k<=T;k++){
scanf("%d%d",&q,&m);
for(int i=2;i<=m;i++){
int sum=i-(i-1)/(k+1)-1;
int pos=sum-ans[sum]+1;
ans[i]=pos+1+(pos-1)/k;
}
for(int i=1;i<=q;i++){
scanf("%d",&n);
cout<<ans[n]<<' ';
}
cout<<'\n';
}
return 0;
}
B.Little Elephant and Broken Sorting(Little Elephant and Broken Sorting)
概率期望狗都不做
规定$ f_{i,j}$表示第 $i$ 位上的数比第 $j$ 位上的数大的概率,n的范围很小,可以直接预处理。
每次交换,需要分类讨论:
1.两个位置都为交换的位置
2.其中一个位置为交换的位置
对于第一种情况,则 $f_{x,y}$ 与 $f_{y,x}$ 的值都为 $0.5$,原因显然。
对于第二种情况,既然有 $0.5$ 的概率更换,就需要将交换成功的概率加上交换失败的概率即可。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int M=5010;
int n,m;
int a[M];
double ans;
double f[M][M];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
f[i][j]=(a[i]>a[j]);
}
}
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
f[x][y]=f[y][x]=0.5;
for(int j=1;j<=n;j++){
if(j!=x&&j!=y){
f[j][x]=f[j][y]=0.5*(f[j][x]+f[j][y]);
f[x][j]=f[y][j]=0.5*(f[x][j]+f[y][j]);
}
}
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(i!=j){
ans+=f[i][j];
}
}
}
printf("%0.9lf",ans);
return 0;
}
C.楼房重建(P4198 楼房重建)
神奇+抽象线段树
我们需要不断的区间查询和区间修改,联想到线段树。
已知当一个建筑的斜率大于前面所有建筑的斜率时,该建筑可以被看到,所以我们进行 $update$ 。
合并操作是难点。
设当前区间为 $c$ ,左区间为 $lc$ ,右区间为 $rc$ 。
首先 $lc$ 的答案是可以直接加入 $c$ 的答案的,原因显然。
但是 $rc$ 的答案比较特殊,可能会被 $lc$ 遮挡,需要分类讨论。
1. 如果c的最大值在lc,那么rc的所有建筑一定都会被阻挡,直接返回lc的答案。
2.如果c的最大值在rc,我们还需要再分:
- 如果rc的左区间的最大值比lc的最大值大,我们直接加上rc的右区间的答案,并递归rc的左区间。
- 否则直接递归rc的右区间即可。
代码如下:
#include<bits/stdc++.h>
#define lc c<<1
#define rc c<<1|1
using namespace std;
const int M=1e5+10;
struct abc{
int l,r,num;
double k;
}t[M<<2];
int n,m;
void add(int l,int r,int c){
t[c].l=l; t[c].r=r;
if(l==r){
return;
}
int mid=(l+r)>>1;
add(l,mid,lc);
add(mid+1,r,rc);
}
int query(double ma,int c){
if(t[c].k<=ma){
return 0;
}
if(t[c].l==t[c].r){
return 1;
}
if(t[lc].k>ma){
return query(ma,lc)+t[c].num-t[lc].num;
}
else {
return query(ma,rc);
}
}
void update(int x,double va,int c){
if(t[c].l==x&&t[c].r==x){
t[c].num=1;
t[c].k=va;
return;
}
int mid=(t[c].l+t[c].r)>>1;
if(x<=mid){
update(x,va,lc);
}
else {
update(x,va,rc);
}
t[c].k=max(t[lc].k,t[rc].k);
t[c].num=t[lc].num+query(t[lc].k,rc);
}
int main(){
scanf("%d%d",&n,&m);
add(1,n,1);
for(int op=1;op<=m;op++){
int x,y;
scanf("%d%d",&x,&y);
update(x,(double)y/x,1);
cout<<t[1].num<<'\n';
}
return 0;
}
D.Yes or No([AGC019F] Yes or No)
人生第二黑
但是讲不清楚,建议出门右转,找大佬小粉兔
肯定不是不想打了