NOIP2012day2题解

6 篇文章 0 订阅
2 篇文章 0 订阅

同余方程:
题目大意:求关于 x 同余方程 ax ≡ 1 (mod b)的最小正整数解。
题解:
就是一道裸的求同余方程……直接贴一个扩展欧几里得即可。根据费马定理&&同余的定理,我们可以得出ax ≡ 1 (mod b)等价于ax-by=1等价于ax+by=gcd(a,b)。
这里给出扩展欧几里得解ax+by=gcd(a,b)的证明(或者说步骤)。
ax1+by1=gcd(a,b)=gcd(b,a%b)=bx2+a%by2=bx2+(a-a/b*b)y2=bx2+ay2-a/b*b*y2=b(x2-a/b*y2)+ay2
所以x1=y2,y1=x2-a/b*y2,因此,我们用递归ex_gcd(b,a%b,y,x),x-=a/b*y即可。
最后,当a=0时,即b=gcd(a,b)时,显然要return了,这时不难发现,当x=0,y=1时是一组整数解。

#include<cstdio>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<algorithm>
using namespace std;
int x,y,a,b;
void gcd(int x,int y,int &a,int &b){
    if(!y){
        a=1;
        b=0;
        return;
    }
    gcd(y,x%y,b,a);
    b-=x/y*a;
}
int main(){
    scanf("%d%d",&x,&y);
    gcd(x,y,a,b);
    if(a<0)a+=y;
    printf("%d",a%y);
    return 0;
}

借教室:
题目大意:有n天,每天只能提供ai个教室,现有m份订单,每份订单为第si天~第ti天每天都借di个教室,现要求满足第i个订单,必须先满足第i-1个订单,求第一个无法满足的订单。n,m<=10^6,ai,di<=10^9.
题解:
由于官方数据实在是太水……我考场上写了一个随随便便能卡成n^2的大暴力居然过的飞快(居然虐了标程!!!)……这里就不献丑了……还是讲正解吧……
首先我们发现一个显然的单调性:当第i个不满足时,第i+1个必定不满足,于是我们可以利用这个性质进行二分答案。
设mid为答案,我们只需判断前mid个订单是否能够全部满足,如果不能,那么就往前二分,并更新答案,否则往后二分。
那么现在就是前mid个订单是否能够全部满足怎么做了……
一个裸的区间修改单点查询是吧?线段树或者树状数组都可做,时间复杂度:O(n (logn)^2),空间复杂度:O(n)。
但是对于n<=10^6的数据,这个时间复杂度是有点卡的……fread也许能够卡过去,但是保险起见,No zuo no die。
现在介绍一个离线求区间修改单点查询的方法:前缀和思想。
对于一段区间s~t需要加x,我们可以设一个前缀和数组b,在b[s]+=x,b[t+1]-=x。再O(n)扫一遍求前缀和,就可以O(1)回答单点查询了。
证明:由于我们求了一遍前缀和,当b[s]+=x时,相当于s~n均加了x,这时我们b[t+1]-=x,也就是t+1~n均减了x,这时就只有s~t的时候加了x了。
时间复杂度:O(n log n),空间复杂度:O(n)。

#include<cstdio>
#include<cstring>
struct node{
    int d,s,t;
}a[1000010];
int i,j,n,m,now,rr[1000010],ans,l,r,mid;
long long s[1000010];
bool pd(int x){
    memset(s,0,sizeof(s));
    for(int i=1;i<=x;i++)s[a[i].s]+=a[i].d,s[a[i].t+1]-=a[i].d;
    for(int i=1;i<=n;i++){
        s[i]+=s[i-1];
        if(s[i]>rr[i])return 1;
    }
    return 0;
}
int main(){
    scanf("%d%d",&n,&m);
    ans=m+1;
    for(i=1;i<=n;i++)scanf("%d",&rr[i]);
    for(i=1;i<=m;i++)scanf("%d%d%d",&a[i].d,&a[i].s,&a[i].t);
    l=1;
    r=m+1;
    while(l<=r){
        mid=(l+r)>>1;
        if(pd(mid))r=mid-1,ans=mid;
        else l=mid+1;
    }
    if(ans>m)putchar('0');
    else printf("-1\n%d",ans);
    return 0;
}

疫情控制:
题目大意:有n个城市构成一棵树,树边上有权值,根节点是1号城市,现有m个军队驻扎在城市中(保证没驻扎在1号城市,不保证m个军队的位置不重复),每个城市管辖的范围为以它为根的子树。现m个军队同时行动,改变它驻扎的位置(不能为根节点),求将所有叶子节点全部管辖的权值和最小值。m,n<=50000.
题解:
这年俩倍增俩二分,还有一道数论,能活下来的人真是强啊……
显然这题的问题能转化为求军队行动的最大权值和的最小值。于是毫不犹豫想到了二分答案……显然若权值和最小值为ans时可以使所有叶子节点全部管辖,权值和最小值为ans+1时也必定可以(大不了浪费1)。
于是乎只需看怎么判断可行性了。
由于它是一棵树,那么一定是越靠近根的结点管理的叶子节点越多,因为对于一个点i,father[i]管理的叶子节点必定>=i管理的,但是我们又不能在1设置,于是只能先让军队尽可能地往1靠,再从走向1的子节点。
但是这样会有一个问题:如果先让所有军队全都到1,且它们不能再去到其它结点了,但是这时我们可以回到从它原先驻扎到结点到1的路径中离1最近的点去,如果按我们刚刚的想法让所有结点都走到1,那么现在就回不来了。
于是我们要记录一下军队在去到1个过程中经过的离1最近的点是哪个。
而且并不是所有的军队都要记录,我们只需要记录那些去到1后剩余时间内回不来了的点。
由于有些军队可能并不能在规定时间内走到1,但是他们也管辖了一部分叶子节点,如果对于那些回不来的军队,他们途径的那个点管辖的所有叶子节点都已经被管辖了的话,就不必再回来了,如果没有,那么让他们回来的结果一定不比最优解差。这个证明比较简单,就不写了。
那么现在需要解决的一个问题就是哪些点是已经被管辖了的。
这个我们可以用深搜,如果对于结点i,它的所有子节点son[i]都被管辖了,那么可以认为这个点也是被管辖了的,于是只要先记录那些不能到达的军队,然后再dfs一遍,再最后确定哪些军队留在1。
现在我们得到了两个序列:呆在1并且可以继续行动的军队,以及还没被管辖到的叶子节点。
我们可以将两个序列按时间从小到大排序,然后一对一扫一遍,如果军队能去到该节点,就去,如果不能,就往下一个军队推,直到两个序列有一个为空。显然如果是军队的序列空了,那么当前时间做不到管辖所有叶子节点,否则可以。
现在只剩下最后一个问题了,一个个节点往上爬的话一次需要O(nm)的时间,所以用一个倍增就好了,优化到O(m log n)。时间复杂度:O(n (log n)^2),空间复杂度:O(n)。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
struct node{
    int d,next,data;
}e[100010],e1[50010];
int n,m,efree,i,q[50010],q1[50010],uscnt,us[50010],yz[50010],newyzcnt,newyz[50010],yzcnt,fayz[50010],a[50010],b[50010],c[50010],efree1,x,y,z,f[50010][20],d[50010][20],l,r,mid,ans;
bool v[50010],v1[50010];
void add(int x,int y,int z){
    e[++efree].d=y;
    e[efree].data=z;
    e[efree].next=q[x];
    q[x]=efree;
}
void add1(int x,int y,int z){
    e1[++efree1].d=y;
    e1[efree1].next=q1[x];
    e1[efree1].data=z;
    q1[x]=efree1;
}
void dfs(int x){
    v[x]=1;
    for(int i=q[x];i;i=e[i].next)
        if(!v[e[i].d]){
            add1(x,e[i].d,e[i].data);
            f[e[i].d][0]=x;
            d[e[i].d][0]=e[i].data;
            dfs(e[i].d);
        }
    if(!e[q[x]].next)yz[++yzcnt]=x;//为了倍增和二分时另一个dfs所做的预处理
}
void pre(){
    for(int i=1;i<=17;i++)
        for(int j=1;j<=n;j++)
            f[j][i]=f[f[j][i-1]][i-1],d[j][i]=d[j][i-1]+d[f[j][i-1]][i-1];
    for(int i=1;i<=yzcnt;i++){
        int k=yz[i];
        while(f[k][0]!=1)
            for(int j=17;j>=0;j--)
                if(f[k][j]!=1){
                    k=f[k][j];
                    break;
                }
        fayz[i]=k;
    }
}
void dfs1(int x){
    v[x]=1;
    bool f=1;
    for(int i=q1[x];i;i=e1[i].next)
        if(!v1[e1[i].d]){
            dfs1(e1[i].d);
            f=0;
            if(v1[e1[i].d])f=1;//如果在dfs后该节点又变成被管辖了的话也需要设为被管辖
        }
    if(f&&q1[x])v1[x]=1;
}
inline bool cmp(int x,int y){return c[x]>c[y];}
inline bool cmp1(int x,int y){return d[x][0]<d[y][0];}
bool pd(int x){
    uscnt=0;
    memset(v,0,sizeof(v));
    memset(v1,0,sizeof(v1));
    for(int i=1;i<=m;i++)b[i]=a[i],c[i]=0;
    for(int i=1;i<=m;i++){
        int k=0;
        while(b[i]!=k){
            k=b[i];
            for(int j=17;j>=0;j--)
                if(d[b[i]][j]+c[i]<=x&&f[b[i]][j]!=1){
                    c[i]+=d[b[i]][j];
                    b[i]=f[b[i]][j];
                    break;
                }
        }
        if(c[i]+2*d[b[i]][0]<=x)c[i]+=d[b[i]][0],b[i]=1,us[++uscnt]=i;
        else
        if(c[i]+d[b[i]][0]>x)v1[b[i]]=1;//记录哪些军队是可以去1,以及去了1回不来的
    }
    dfs1(1);//第一遍dfs1求哪些节点需要回不来的军队守着
    for(int i=1;i<=m;i++)
    if(d[b[i]][0]+c[i]<=x&&v1[b[i]]&&b[i]!=1)c[i]+=d[b[i]][0],b[i]=1,us[++uscnt]=i;
    else v1[b[i]]=1;
    memset(v,0,sizeof(v));
    dfs1(1);//第二遍dfs1求最终还没被管辖的叶子节点
    newyzcnt=0;
    for(int i=1;i<=yzcnt;i++)
        if(v[fayz[i]]&&!v1[fayz[i]])newyz[++newyzcnt]=fayz[i],v[fayz[i]]=0;
    if(uscnt<newyzcnt)return 0;
    sort(newyz+1,newyz+1+newyzcnt,cmp1);
    sort(us+1,us+1+uscnt,cmp);
    int i=1,j=1;
    while(j<=newyzcnt&&i<=uscnt){
        if(x-c[us[i]]>=d[newyz[j]][0])j++,i++;
        else i++;
    }//双指针
    if(j>newyzcnt)return 1;
    else return 0;
}
int main(){
    scanf("%d",&n);
    for(i=1;i<n;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    f[1][0]=1;
    dfs(1);
    r=1e9;
    pre();//预处理倍增
    scanf("%d",&m);
    for(i=1;i<=m;i++)scanf("%d",&a[i]);
    while(l<=r){
        mid=(l+r)>>1;
        if(pd(mid))ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%d",ans);
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值