NOIP2017提高组 模拟赛18(总结)
程序员文章站
2022-03-14 19:22:02
...
NOIP2017提高组 模拟赛18(总结)
第一题 火柴
【题目描述】
【解题思路】
从左往右扫一遍,优先满足第i位,若ai>ave,则将多余的火柴移到第i+1位;若ai<ave,则将i+1位的ave-ai个移到第i位(即使将第i+1位变成负数也没关系,因为第i+1位也可以从后面拿),统计移动次数即可。
【代码】
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=50050;
ll d[N],sum,ans;
int n;
int main()
{
freopen("2247.in","r",stdin);
freopen("2247.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&d[i]),sum+=d[i];
sum=sum/n;
for(int i=1;i<n;i++)
{
if(d[i]>sum) d[i+1]+=d[i]-sum,ans+=d[i]-sum,d[i]=sum;
else d[i+1]-=sum-d[i],ans+=sum-d[i],d[i]=sum;
}
printf("%lld\n",ans);
return 0;
}
第二题 管理者
【题目描述】
【解题思路】
先求出最小生成树,假如ui->vi必须升级,则在ui->vi的路径上找出最大的边,替换掉就行了。
如何快速求路径最大值呢?
LCT可以很好的解决这一问题(滑稽)
其实用树上倍增就足够了。
【代码】
#include<cstdio>
#include<algorithm>
#define imax(a,b) ((a>b)?(a):(b))
using namespace std;
typedef long long ll;
const int N=200500;
struct tree
{
tree *f,*c[2],*pp;
int val,mx;
bool flip;
int d() { return (f->c[1]==this); }
void sc(tree *x,int d) { (c[d]=x)->f=this; }
} nil[N<<2],*ro[N<<2],*stack[N<<2];
struct data
{
int l,r,cost,id;
} road[N];
int f[N],n,m,cnt;
ll ans;
void down(tree *x)
{
if(x==nil) return;
if(x->flip)
{
swap(x->c[0],x->c[1]);
if(x->c[0]!=nil) x->c[0]->flip^=1;
if(x->c[1]!=nil) x->c[1]->flip^=1;
x->flip=0;
}
}
void up(tree *x)
{
if(x==nil) return;
x->mx=x->val;
if(x->c[0]!=nil) x->mx=imax(x->mx,x->c[0]->mx);
if(x->c[1]!=nil) x->mx=imax(x->mx,x->c[1]->mx);
}
tree *newtree()
{
nil[++cnt]=nil[0];
return nil+cnt;
}
void rotate(tree *x)
{
int d=x->d();
tree *y=x->f;
y->sc(x->c[!d],d);
if(y->f==nil) x->f=nil; else y->f->sc(x,y->d());
x->sc(y,!d);
x->pp=y->pp;
y->pp=nil;
up(y); up(x);
}
void splay(tree *x)
{
int hy=1; stack[hy]=x;
for(;stack[hy]->f!=nil;hy++) stack[hy+1]=stack[hy]->f;
for(int i=hy;i>=1;i--) down(stack[i]);
for(tree *y;x->f!=nil;)
{
y=x->f;
if(y->f!=nil)
(y->d()^x->d())?rotate(x):rotate(y);
rotate(x);
}
}
void access(tree *x)
{
tree *y=nil;
while(x!=nil)
{
splay(x);
if(x->c[1]!=nil)
{
x->c[1]->f=nil;
x->c[1]->pp=x;
}
x->c[1]=y;
if(y!=nil)
{
y->f=x;
y->pp=nil;
}
up(x);
y=x;
x=x->pp;
}
}
void evert(tree *x)
{
access(x);
splay(x);
x->flip^=1;
}
void link(tree *x,tree *y)
{
evert(y);
splay(y);
y->pp=x;
}
bool cmp(data A,data B) { return (A.cost<B.cost); }
bool cmp1(data A,data B) { return (A.id<B.id); }
int find(int x) { return ((f[x]<0)?(x):(f[x]=find(f[x]))); }
void uni(int now)
{
int x1=find(road[now].l),x2=find(road[now].r);
if(x1==x2) return;
ans+=road[now].cost;
link(ro[road[now].l],ro[n+road[now].id]);
link(ro[road[now].r],ro[n+road[now].id]);
if(f[x1]>f[x2]) swap(x1,x2);
f[x1]+=f[x2]; f[x2]=x1;
}
int findmax(tree *x,tree *y)
{
evert(x); access(y);
splay(y); return y->mx;
}
int main()
{
freopen("2248.in","r",stdin);
freopen("2248.out","w",stdout);
cnt=0; nil->f=nil->pp=nil->c[0]=nil->c[1]=nil;
nil->mx=nil->val=0; nil->flip=0;
scanf("%d%d",&n,&m); ans=0ll;
for(int i=1;i<=n;i++) ro[i]=newtree();
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&road[i].l,&road[i].r,&road[i].cost),road[i].id=i;
ro[n+i]=newtree();
ro[n+i]->val=ro[n+i]->mx=road[i].cost;
}
sort(road+1,road+1+m,cmp);
for(int i=1;i<=n;i++) f[i]=-1;
for(int i=1;i<=m;i++) uni(i);
sort(road+1,road+1+m,cmp1);
for(int i=1;i<=m;i++)
{
int yu=findmax(ro[road[i].l],ro[road[i].r]);
printf("%lld\n",ans-yu+road[i].cost);
}
return 0;
}
第三题 数学家
【题目描述】
【解题思路】
记g[h][i]为长度为h的南墙,用i块草坪去铺的最小面积。
记f[h][i][j]为长度为h的南北墙,北墙用i块草坪,南墙用j块草坪去铺的最小面积。
h1<h,j1<j
时间复杂度:O(N^5) 显然超时……
其实这具有决策单调性,因为
假若f[h][i-1][j]的决策是h1,j1,f[h][i][j]的决策是h2,j2,必定存在h2>h1,j2>j1。
利用这一决策单调性可以除去一维,时间复杂度:O(N^4)
加了许多优化,程序跑的飞快……
千万记得判是否越界,例如草坪数要永远不超过墙的长度。
没有注意,WA了好多次
【代码】
#include<cstdio>
#include<algorithm>
#define imax(a,b) ((a>b)?(a):(b))
#define imin(a,b) ((a<b)?(a):(b))
using namespace std;
typedef long long ll;
const int N=120;
double f1,f2;
int n,m;
struct data { int l,p; } d[N][N][N];
double g[N][N],f[N][N][N];
int main()
{
freopen("2249.in","r",stdin);
freopen("2249.out","w",stdout);
scanf("%lf%lf%d%d",&f1,&f2,&n,&m);
for(int i=1;i<=100;i++)
{
g[i][0]=1e9;
g[i][1]=i*i;
for(int j=2;j<=i;j++)
{
int yu=i/j;
g[i][j]=g[i-yu][j-1]+yu*yu;
}
}
for(int i=1;i<=100;i++)
{
for(int j=1;j<=m;j++)
{
f[i][j][1]=i*i*f1+g[i][j]*f2;
d[i][j][1].l=0; d[i][j][1].p=0;
}
}
for(int h=2;h<=100;h++)
{
int mi=imin(h,m),mj=imin(h,n);
for(int i=2;i<=mi;i++)
{
for(int j=2;j<=imin(i,mj);j++)
{
int sh=imax(1,d[h][i][j-1].l),si=imax(imax(1,d[h][i][j-1].p),j-1);
f[h][i][j]=1e9;
for(int h1=sh;h1<=h;h1++)
{
/*if(h==100 && i==100 && h1==50)
printf("ok\n");*/
double getans=1e9; int kl,kp;
int gh=h-h1;
int ip=imax(i-gh,si),mip=imin(h1,i);
for(int i1=ip;i1<=mip;i1++)
{
double yu=f[h1][i1][j-1]+gh*gh*f1+g[gh][i-i1]*f2;
if(yu>getans) break;
getans=yu;
kl=h1; kp=i1;
}
if(getans<f[h][i][j])
{
f[h][i][j]=getans;
d[h][i][j].l=kl; d[h][i][j].p=kp;
}
}
//printf("%d %d %d %.lf\n",h,i,j,f[h][i][j]);
}
}
}
printf("%.1lf\n",f[100][m][n]);
return 0;
}
上一篇: 判断满二叉树
下一篇: 1.0-Flink概念:基础