NOIP2017提高组 模拟赛18(总结)

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块草坪去铺的最小面积。
   g[h][1]=hhf2    g[h][i]=g[hh/i][i1]+(h/i)2f2
   f[h][i][j]=max(f[h1][i1][j1]+g[hh1][jj1]+(hh1)2f1)
  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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值