NOIP2012复赛DAY2

6 篇文章 0 订阅
4 篇文章 0 订阅

NOIP2012——DAY2

1、同余方程

【题目分析】
其实也不用分析了,这道题是作为我们的数论入门题来练的。如果在考场上碰到这种恶心的数学题,不管敲得对敲不对,反正一定要把暴力先敲好。
http://blog.csdn.net/ycdfhhc/article/details/44260687
作为一个蒟蒻,想解释但还是心有余而力不足啊。不懂的小伙伴自行学习,早就懂的小伙伴也可以温习一下。

【代码】

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
ll ex_gcd(ll a,ll b,ll &x,ll &y){
    ll d=a;
    if(b){
        d=ex_gcd(b,a%b,y,x);
        y-=a/b*x;
    }else x=1,y=0;
    return d;
}
int main(){
    ll a,b,x,y;
    cin>>a>>b;
    int ans=ex_gcd(a,b,x,y);
    while(x<=0)x+=b;
    cout<<x<<endl;
    return 0;
}

2、借教室

【题目分析】
都DAY2了,还能不能让我愉快地敲一个暴力?
不多说,45分暴力代码先给出。

【45分代码】

#define M 1000005
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int cnt[M];
int main(){
    int i,j,n,m;
    scanf("%d %d",&n,&m);
    for(i=1;i<=n;i++)scanf("%d",&cnt[i]);
    for(i=1;i<=m;i++){
        int d,l,r;
        scanf("%d %d %d",&d,&l,&r);
        for(j=l;j<=r;j++){
            cnt[j]-=d;
            if(cnt[j]<0)break;
        }if(j!=r+1)break;
    }
    if(i==m+1)puts("0");
    else puts("-1"),printf("%d\n",i);
    return 0;
}

枚举->贪心,貌似都不行。
我们的目标是完成所有订单。
题目里虽然没有说最大最小,之类的话,只说了,“如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配”,如果这一份订单如果无法完成,那么我们假设的可以完成所有订单这一目标也一定无法完成。
如果先定一个小目标,看他能不能完成。如果小目标不能完成,大目标也一定不能完成。所以,具有单调性。
思路就变成了二分答案。
同时,在假设当前的目标能够完成的时候,一个一个减太慢了,我们学过差分前缀和(也就是刷漆),将每次查询的复杂度降到了O(n)。
复杂度O(n*logn)。

【代码】

#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#define M 1000005
#include<iostream>
#include<algorithm>
using namespace std;
void Rd(int &res){
    char c;res=0;
    while(c=getchar(),!isdigit(c));
    do{res=(res<<3)+(res<<1)+(c^48);}while(c=getchar(),isdigit(c));
}
struct quertion{int l,r,d;}q[M];
int n,m,ans=0,a[M];
long long b[M];
bool chk(int x){
    bool f=1;
    long long sum=0;
    memset(b,0,sizeof(b));
    for(int i=1;i<=x;i++){
        b[q[i].l]+=q[i].d;
        b[q[i].r+1]-=q[i].d;
    }
    for(int i=1;i<=n;i++){
        sum+=b[i];
        if(sum>a[i])return 0;
    }return 1;
}
int main(){
    int i;
    Rd(n),Rd(m);
    for(i=1;i<=n;i++)Rd(a[i]);
    for(i=1;i<=m;i++)Rd(q[i].d),Rd(q[i].l),Rd(q[i].r);
    int l=1,r=m;
    while(l<=r){
        int mid=l+r>>1;
        if(!chk(mid))ans=mid,r=mid-1;
        else l=mid+1;
    }
    if(!ans)puts("0");
    else printf("-1\n%d\n",ans);
    return 0;
}

天啊撸,刚打好的疫情控制题解没了,都怪一个叫yahong的变态!!

3、疫情控制

【题目分析】
从枚举军队最后的状态入手,可以水到20分。
需要将有限的军队最大的发挥它们的作用,就要不断向上攀爬,使他能满足更多的边疆城市,这是贪心的思想。
从常识中可以知道,若是疫情被能控制,时间越长,更有可能。而我们不可能按照时间的推移来检验该时间是否可行(显然超时),又有“请问最少需要多少个小时才能控制疫情”,得出二分答案的做法。
但是首都的儿子中有些点是本来就没有军队的,所以将军队又分为了需要翻过首都支援的和留在原地的。而我们又希望剩余时间更多的来支援,这还是贪心的思想。
接下来就是如何实现的问题了。

【代码】

#define M 50005
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define oo 1e15
using namespace std;
void Rd(int &res){
    res=0;char c;
    while(c=getchar(),!isdigit(c));
    do res=(res<<3)+(res<<1)+(c^48);
    while(c=getchar(),isdigit(c));
}
struct node{int v,w,nxt;}st[M<<1];
int fa[M],army[M],head[M],rest[M];
int n,m,etop=0;
bool mk[M];
ll dis[M];
void add_edge(int u,int v,int w){
    st[++etop]=(node){v,w,head[u]},head[u]=etop;
    st[++etop]=(node){u,w,head[v]},head[v]=etop;
}
void dfs(int u,int pre,ll d){
    dis[u]=d,fa[u]=pre;
    for(int j=head[u];~j;j=st[j].nxt){
        node now=st[j];
        if(now.v!=pre)dfs(now.v,u,d+now.w);
    }
}
struct cmp{bool operator()(int &a,int &b)const{return dis[a]<dis[b];}};
bool check(int u,int pre,bool flag){
    if(flag)return 1;
    bool f=0;
    for(int j=head[u];~j;j=st[j].nxt){
        int v=st[j].v;
        if(v==pre)continue;
        f=1;
        if(!check(v,u,flag|mk[v]))return 0;
    }return f;
}
bool check(ll T){
    memset(mk,0,sizeof(mk));
    memset(rest,-1,sizeof(rest));
    priority_queue<int>q1;
    priority_queue<int,vector<int>,cmp>q2;
    for(int i=1;i<=m;i++){
        int u=army[i];
        while(fa[u]&&fa[u]!=1&&dis[army[i]]-dis[fa[u]]<=T)u=fa[u];
        if(fa[u]!=1||T<dis[army[i]])mk[u]=1;
        else{
            int v=T-dis[army[i]];
            if(rest[u]==-1)rest[u]=v;
            else{
                if(rest[u]>v)swap(rest[u],v);
                q1.push(v);
            }
        }
    }
    for(int j=head[1];~j;j=st[j].nxt){
        int v=st[j].v;
        if((~rest[v])&&(check(v,1,mk[v])||rest[v]>=dis[v]))q1.push(rest[v]);
        else if(~rest[v])mk[v]=1;
    }
    for(int j=head[1];~j;j=st[j].nxt){
        int v=st[j].v;
        if(!check(v,1,mk[v]))q2.push(v);
    }
    while(!q1.empty()&&!q2.empty()){
        int v=q2.top();
        if(q1.top()>=dis[v]){
            mk[v]=1;
            q1.pop();
            q2.pop();
        }else q1.pop();
    }return check(1,0,0);
}
int main(){
    Rd(n);
    int cnt=0;
    memset(head,-1,sizeof(head));
    for(int i=1,u,v,w;i<n;i++){
        Rd(u),Rd(v),Rd(w);
        if(u==1||v==1)cnt++;
        add_edge(u,v,w);
    }Rd(m);
    for(int i=1;i<=m;i++)Rd(army[i]);
    if(cnt>m){puts("-1");return 0;}
    dfs(1,0,0);
    int l=0;
    ll r=oo;
    ll ans=-1;
    while(l<=r){
        int md=l+r>>1;
        if(check(md))ans=md,r=md-1;
        else l=md+1;
    }cout<<ans<<endl;
    return 0;
}

虽然能过,但是有点慢,是因为想偷点懒,所以在向上的时候没有“跳”。

【思路】
1、写过了。
2、枚举->二分
3、枚举->贪心->二分答案

总的来说,如果有恶心的数学题,还是乖乖地先去敲出暴力再去想正解吧,虽然说可能性并不大。同时二分答案也是解题的一个思路之一,在想问题的时候一定要往上面去想一想,套一套。还有就是第三题,有了思路并不代表有了一切,最终还是要把它实现出来啊。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值