NOIP2012提高组Day2

NOIP2012Day2

一、同余方程

数据:对于100%,a、b∈[1,2e9]

模板题,实在不行手推一下也行。

二、借教室

数据:对于 30%,n,m∈[1,1000]
对于 100%,n,m∈[1,1e6]

30分暴力模拟就行。

看完题想到的就是线段树,把区间最小值存下来就行了。当tree[1]<0(tree[1]就是1-n这段区间)时,就直接输出。这样写只有95分(如果服务器不快的话),线段树的常数太大了,会卡。

正解有两种:一种是二分+ 差分。二分显然,人越多越容易不满足,然后差分就行了。第二种只是差分,但是要回撤差分。先把全部的差分用上,然后循环,当一个位置教室不够时,人从后往前回撤,把他们的差分去掉,如果这个点在撤掉的人的区间里,还要加上一个值。这样一边扫过去后,如果回撤到第k个人,那么第k+1个人就是不满足的那个;当k==m时都可以。这样就是n*m的。

Code:

#include<bits/stdc++.h>
#define ll long long
#define M 1000005
using namespace std;
ll A[M],cnt[M];
struct node{int l,r;ll x;}B[M];
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%lld",&A[i]);
    for(int i=1;i<=m;i++){
        scanf("%lld%d%d",&B[i].x,&B[i].l,&B[i].r);
        cnt[B[i].l]+=B[i].x;
        cnt[B[i].r+1]-=B[i].x;
    }
    ll k=0;
    int r=m;//从后往前
    for(int i=1;i<=n;i++){
        k+=cnt[i];
        if(A[i]<k)//教室不够了
            while(r>0&&k>A[i]){
                if(B[r].l<=i&&B[r].r>=i)k-=B[r].x;//如果在区间内要少掉这个人的影响
                cnt[B[r].l]-=B[r].x;
                cnt[B[r].r+1]+=B[r].x;//回撤
                r--;
            }
    }
    if(r==m)puts("0");else printf("-1\n%d\n",r+1);
    return 0;
}

三、疫情控制

数据:对于 20%,n∈[1,10]
对于100%,m,n∈[1,5e4]

20分dfs每个人在每个位置时能否达到目的就行了。

正解是二分+贪心+倍增。考试时想到了,但是贪心在这里是一个很烦的东西。因为谈的不好分数就会很玄学。考试时的那个贪心有大问题,随便造一组数据就可以卡掉,但是还有40分。然后我开始改贪心的那一块,总共写了四种不同的贪心方法,有20、40、60、80,十分齐全。最后终于改出来了。

想法是二分出一个最远的距离,然后把每个警卫往上走。然后从根节点判断根节点下的节点(深度为2的节点)所管的子树是否已经隔离。用两个vector装一些值,一个是装哪些警卫到了主城(到了主城才能到其他子树上),一个是装哪些子树没有被隔离。然后贪心的判断能不能分配警卫到主城。

但是有特殊情况:当一个警卫回到了他来的那个子树上,是不能算距离的。这样的话还要多记录一个id,表示这个警卫从哪里来。

代码多而复杂,尤其是贪心那一块

Code:

#include<bits/stdc++.h>
#define ll long long
#define M 50005
using namespace std;
int n,m,B[M];
struct node{int to;ll v;};
vector<node>edge[M];
int fa[20][M],dep[M],F[M];
ll dis[20][M];
struct INIT{//预处理出点的父亲节点,距离,深度,倍增,位于哪棵子树
    void f1(int x,int fa1,int F1){//F1是这个点往上除根节点外的最远祖先(即深度为2的祖先)
        if(dep[fa1]+1==2)F1=x;
        dep[x]=dep[fa1]+1;fa[0][x]=fa1;F[x]=F1;
        for(int i=0;i<(int)edge[x].size();i++){
            int y=edge[x][i].to;
            if(y==fa1)continue;
            f1(y,x,F1);
            dis[0][y]=edge[x][i].v;
        }
    }
    void f2(){
        for(int j=1;j<20;j++)
            for(int i=1;i<=n;i++){
                fa[j][i]=fa[j-1][fa[j-1][i]];
                dis[j][i]=dis[j-1][i]+dis[j-1][fa[j-1][i]];
            }
    }
    void solve(){
        f1(1,0,0);
        f2();
    }
}Init;
int Q[M],Q1[M];
struct node1{int x,id;ll dis;}C[M];
struct node2{
    int id;ll dis;
    bool operator < (const node2 &x)const{
        return dis<x.dis;
    }
};
vector<node2>H,G;
bool f(int x){
    if(Q[x]&&x!=1)return Q[x]=1;
    if(edge[x].size()==1&&x!=1)return Q[x]=0;
    int p=1;
    for(int i=0;i<(int)edge[x].size();i++){
        int y=edge[x][i].to;
        if(y==fa[0][x])continue;
        p=p&f(y);
    }
    return Q[x]=p;
}
bool check(ll x){//贪心——判断
    H.clear();G.clear();
    memset(Q,0,sizeof Q);
    memset(Q1,0,sizeof Q1);
    for(int i=1;i<=m;i++){//把每个警卫往上走
        C[i]=(node1){B[i],F[B[i]],x};//F[B[i]]是第i个警卫所在的子树
        for(int j=19;j>=0;j--)//倍增求最远向上会到哪里
            if(fa[j][C[i].x]>0&&C[i].dis>=dis[j][C[i].x])
                C[i].dis-=dis[j][C[i].x],C[i].x=fa[j][C[i].x];
        Q[C[i].x]++;//记录现在哪几个位置有警卫
    }
    if(f(1))return 1;//按照现在的情况先进行判断,判断哪几棵子树已经被隔离了
    for(int i=1;i<=m;i++)//把在主城的警卫存起来
        if(C[i].x==1)H.push_back((node2){C[i].id,C[i].dis});
    for(int i=0;i<(int)edge[1].size();i++)//把深度为2的没被隔离的子树的跟存起来
        if(Q[edge[1][i].to]==0)G.push_back((node2){edge[1][i].to,edge[1][i].v});
    sort(H.begin(),H.end());sort(G.begin(),G.end());//按照权值排序,重载运算符看上面
    if(G.size()>H.size())return 0;//如果警卫不够直接返回
    int l1=0;
    for(int i=0;i<(int)H.size();i++){
        while(l1<(int)G.size()){
            if(Q1[l1]){l1++;continue;}//如果这个位置已经有人跳掉
            if(G[l1].id==H[i].id||G[l1].dis<=H[i].dis)break;//如果可以从主城过去或者回到自己的子树上
            l1++;
        }
        if(l1==(int)G.size()){l1=0;continue;}
        Q1[l1]=1;//这个子树已经有人,标记掉
        if(G[l1].id==H[i].id)l1=0;//如果是回到子树上要重新
        else l1++;//否则往后(因为是有序的)
    }
    for(int i=0;i<(int)G.size();i++)if(!Q1[i])return 0;//判断是不是全部都有人
    return 1;
}
int main(){
    scanf("%d",&n);
    ll l=0,r=0;
    for(int i=1;i<n;i++){
        int x,y;ll z;
        scanf("%d%d%lld",&x,&y,&z);
        edge[x].push_back((node){y,z});
        edge[y].push_back((node){x,z});
        r+=z;
    }
    Init.solve();
    scanf("%d",&m);
    for(int i=1;i<=m;i++)scanf("%d",&B[i]);
    ll ans=-1;
    while(l<=r){//二分
        ll mid=(l+r)/2;
        if(check(mid))r=mid-1,ans=mid;
        else l=mid+1;
    }
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值