这次的离线赛不是内部的题,可以放出来了。
A题:
这题作为联赛第一题是偏难的,这题其实是有联赛第二题难度
反倒是B题水的一B。。
我们可以清晰的知道,
本题需要求解的是:
Σsum[i]Σnum[i]
的最大值
由于数据范围的缘故,
直接暴力肯定不行(这不是废话)
我首先看到这题想到的是贪心
对于每行i都选取该值最大的
最后发现这是个错误的贪心
再仔细想想这题貌似不能贪心
于是想到了dp
然而这题dp是n^2*m
会炸(不过倒是有40分)
于是乎。。
我们知道其实这种题型其实解法无非两种
1.枚举分子(母),贪心分母(子)
2.就是这题的解法了
我们假设可以挖金矿时平均值可以达到k
于是可以得出:
Σsum[i]Σnum[i]
≥k
于是乎
Σsum[i]≥k*Σnum[i]
and then:
Σsum[i]-k*Σnum[i]≥0
end!!!!!!
Σ(sum[i]-k*num[i])≥0
于是在二分答案之后,只需在每行找出sum-k*num的最大值就行了
一波代码来袭:
#include<bits/stdc++.h>
int main(){
int n,m;
scanf("%d %d",&n,&m);
int a[n+5][m+5];double l=0,r=1e14;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
while(r-l>=0.00001){//“for(int i=0;i<100;i++){”其实也可以,但是太骚。。而且慢
double mid=(l+r)/2,res=0;
for(int i=1;i<=n;i++){
double mx=0,tmp=0;
for(int j=1;j<=m;j++){
tmp+=a[i][j]-mid;
if(tmp>mx||j==1)mx=tmp;
}res+=mx;
}if(res>=0)l=mid;
else r=mid;
}printf("%.4lf\n",l);
return 0;
}
B题:道路规划
话说其实刚看到这题的时候
我是被它所谓的交叉给吓到了
但其实只要把南部城市当成val,
映射一下,LIS就能秒掉这题了
水题不解释。。。
代码:
#include<bits/stdc++.h>
using namespace std;
#define M 100005
int a[M],b[M],id[M],dp[M],val[M];
int n;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++){scanf("%d",&b[i]);id[b[i]]=i;}
val[0]=n+1;
for(int i=1;i<=n;i++)val[n-i+1]=id[a[i]];
int ans=0,len=0;dp[++len]=val[1];
for(int i=2;i<=n;i++){//标准O(n*logn)LIS
if(val[i]>dp[len])dp[++len]=val[i];
else {
int tmp=upper_bound(dp+1,dp+len+1,val[i])-dp;
dp[tmp]=val[i];
}
}printf("%d\n",len);
return 0;
}
C题:排队
话说这题其实也不是很难
考试的时候其实是想到倍增了的
但是因为 我是弱鸡的缘故 我逗比的认为O(n)的1号操作会超时,于是就没敲
其实这题的解法是很好想的
完全不需要线段树,数状数组什么的
其实只用后续遍历成一个数组以后
2操作倍增跳不解释
1操作其实根本不用管那么多,直接一个个塞就行了
(一次1操作最多可以塞入n个数,2操作却只能拿掉1个数。。。)
于是乎。。
剩下的就只有模拟了
代码:
#include<bits/stdc++.h>
using namespace std;
#define M 100005
#define S 22
vector<int>edge[M];
int k,a[M],mp[M],mark[M],fa[M][S];
priority_queue<int>Q;
void dfs(int x,int pre){
for(int i=0;i<edge[x].size();i++){
int y=edge[x][i];
if(y==pre)continue;
dfs(y,x);
}a[++k]=x;//线映射树
mp[x]=k;//树映射线
for(int i=0;i<edge[x].size();i++){
int y=edge[x][i];
if(y==pre)continue;
fa[mp[y]][0]=mp[x];//跳一步到达father
}
}
int main(){
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<n;i++){
int x,y;
scanf("%d %d",&x,&y);
edge[x].push_back(y);
edge[y].push_back(x);
}for(int i=1;i<=n;i++)sort(edge[i].begin(),edge[i].end());//先去小序号的结点
dfs(1,0);
for(int i=1;(1<<i)<=n;i++)
for(int j=1;j<=n;j++)//倍增
if(fa[j][i-1]&&fa[fa[j][i-1]][i-1])fa[j][i]=fa[fa[j][i-1]][i-1];
for(int i=1;i<=n;i++)Q.push(-i);//大顶堆->小顶堆
while(m--){
int f,x;
scanf("%d %d",&f,&x);
if(f==1){
int id;
while(x--){
id=-Q.top();Q.pop();
mark[id]=1;//id被占用
}printf("%d\n",a[id]);
}else{
int ans=0;x=mp[x];
for(int i=S-1;i>=0;i--)//倍增跳至x点已被占用的最大父亲
if(fa[x][i]&&mark[fa[x][i]]){
ans+=1<<i;
x=fa[x][i];
}
mark[x]=0;Q.push(-x);//这个点空闲
printf("%d\n",ans);
}
}return 0;
}