2017/9/2 离线赛

来源:互联网 发布:sql库位库存分配 编辑:程序博客网 时间:2024/05/24 04:37

T1 UOJ#12 猜数

    这题简直是送分题,代码又短又好想,我这样说怕不是要被打。

    这题应该算是小学奥数题,由于ab都是g的倍数,而且ab=gl,所以ab/g肯定是g的倍数,即lg的倍数。接下来就是一个性质:当两个数的乘积一定时,两个数的差越大,和就越大。下面是简单的证明:

    我们假设两个数分别是ab,已知ab=k。通过均值不等式我们可以知道a+b2ab,当且仅当a=b时成立,也就是说a+b的最小值是2ab,此时a=b=ab。那么a+b2ab就是a+b与最小值的差,化简可得a+b2ab=(ab)2,所以|ab|越大,a+b就越大,又因为ab=k,所以a越小,b越大,a+b越大。

    知道了这些,题目就很简单了,因为ab都是g的倍数,所以a最小是g,即a+b的最大值就是g+la+b的最小值又是gl,关键就是怎么求gl了,数据范围是[1,1018],把gl乘起来肯定超long long,于是要用第一段中的性质了,由于gl是个完全平方数,g又整除l,所以l/g肯定是完全平方数,我们求出l/g*g或者l/l/g即可。

int T;ll a,b;int main(){    Rd(T);    while(T--){        Rd(a);Rd(b);        ll tmp=sqrt(b/a);        Pt(a*tmp+b/tmp),putchar(' '),Pt(a+b),putchar('\n');    }    return 0;}

T2 任务

    dp是显然的,考试的时候敲了70分和100分的两档,但是100分的敲炸了,我当时存了两个机器的时间差,因为知道不能超过3000,但是又想到会有负值,就存了6000,然后就变得很麻烦了,实际只要再存一下哪个大就行了。下面我就把两档都说一下吧。

    70分:状态很简单,就是当前两个机器运行到什么时候。一个存在dp状态里,一个存在dp值里就行了。我们定义dp[i][j][k]i表示前i个任务,j表示当前任务在A机器还是在B机器运行,k表示A机器运行的时间,值表示B机器运行的最短时间。我们用刷表法更新dp,对于上一个任务的状态dp[i1][j][k],我们只要把当前的任务放到A和放到B的情况都更新一遍就好了,当然要注意一点:当前任务至少要加在上一个任务开始进行的时刻,具体看代码。

    100分: 思路很相似,都是存下两个机器的运行时间,但是存的方法不一样,因为我们知道,不可能两个相邻两个任务的时间差超过3000的,所以只要在状态里存一下差值即可。

int n;int A[N],B[N];struct P1{    int dp[2][2][60005];    void check(int &a,int b){        if(a==-1||a>b)a=b;    }    void solve(){        bool cur=0;        memset(dp[cur],-1,sizeof(dp[cur]));        dp[cur][0][A[1]]=0;        dp[cur][1][0]=B[1];        for(int i=2;i<=n;i++){            cur=!cur;            memset(dp[cur],-1,sizeof(dp[cur]));            for(int j=0;j<2;j++)            for(int k=0;k<=60000;k++)            if(~dp[!cur][j][k]){                int tmp=dp[!cur][j][k];                if(j){                    check(dp[cur][0][max(k,(tmp-B[i-1]))+A[i]],tmp);                    check(dp[cur][1][k],tmp+B[i]);                }else{                    check(dp[cur][0][k+A[i]],tmp);                    check(dp[cur][1][k],max(tmp,k-A[i-1])+B[i]);                }            }        }        int ans=-1;        for(int i=0;i<2;i++)        for(int j=0;j<=60000;j++)        if(~dp[cur][i][j])        check(ans,max(j,dp[cur][i][j]));        Pt(ans);        putchar('\n');    }}P1;struct P2{    int dp[2][2][M];    void check(int &a,int b){        if(a==-1||a>b)a=b;    }    void solve(){        bool cur=0;        memset(dp[cur],-1,sizeof(dp[cur]));        dp[cur][0][A[1]]=A[1];        dp[cur][1][B[1]]=B[1];        for(int i=2;i<=n;i++){            cur=!cur;            memset(dp[cur],-1,sizeof(dp[cur]));            for(int j=3000;j>=0;j--){                if(~dp[!cur][0][j]){                    check(dp[cur][0][A[i]],dp[!cur][0][j]+A[i]);                    if(B[i]>=j)check(dp[cur][1][B[i]-j],dp[!cur][0][j]+B[i]-j);                    if(B[i]<=j)check(dp[cur][0][j-B[i]],dp[!cur][0][j]);                }                if(~dp[!cur][1][j]){                    check(dp[cur][1][B[i]],dp[!cur][1][j]+B[i]);                    if(A[i]>=j)check(dp[cur][0][A[i]-j],dp[!cur][1][j]+A[i]-j);                    if(A[i]<=j)check(dp[cur][1][j-A[i]],dp[!cur][1][j]);                }            }        }        int ans=-1;        for(int i=0;i<2;i++)        for(int j=0;j<=3000;j++)        if(~dp[cur][i][j])        check(ans,dp[cur][i][j]);        Pt(ans);        putchar('\n');    }}P2;bool chk1(){    if(n>200)return false;    for(int i=1;i<=n;i++)    if(A[i]>=300||B[i]>=300)return false;    return true;}int main(){    Rd(n);    for(int i=1;i<=n;i++){        Rd(A[i]);Rd(B[i]);    }    if(chk1())P1.solve();    else P2.solve();    return 0;}

T3 图操作

    这题考试的时候就敲了50分,然后忘开long long只有30分了,实际只要对拍一下最大的数据就可以找出来的错误,我却犯了,实在是该打。我是切了四档写的,那就都讲讲吧。

    30分:由于加边是递增的,因此求最小生成树的时候就免了排序,少了一个log的复杂度。所以把边存下来,每次O(m)求一下最小生成树就ok了。但是对于Delete操作就很麻烦了,我们怎么返回去呢,其实很简单,把每次操作后的状态存下来就好了反正n就只有1000。

    50分:只有Add操作,每次加完之后合并一下两个点就好了,有答案就输出,没答案就输出0,但是答案会超int,所以要开long long

    70分:没有Return操作,但有Delete操作,我们仅仅用并查集是不能做到把一个集合拆成两半的,但我们可以用一个类似的方法,每次合并时存一下当前点在合并前所在的集合编号,拆的时候就可以直接在当前集合删掉这个点,再放回原来的集合就行了。这里我们用正向表来存一个集合,将集合内的点连到集合的编号即可。具体操作看代码。

    100分:其实和70分方法一样,就多了个Return操作,我们只要考虑Return怎么操作就行了。我们定义Del(i)表示删掉权值前i的边,那么当Return操作前面的是Add时就调用Del(1),如果前面是Delete操作那就特判一下,在Delete操作时并不进行删除,因为Return操作会返回。

struct opr{    int f,a,b;}Q[M];int n,m;struct P1{    struct edge{        int a,b,c;    };    struct node{        edge e[1005];        int sz,res;        node(){sz=0;}    }s[1005];    int fa[1005],sz[1005];    int getfa(int v){        if(fa[v]==v)return v;        return fa[v]=getfa(fa[v]);    }    bool same(int x,int y){        return getfa(x)==getfa(y);    }    void merge(int x,int y){        int px=getfa(x),py=getfa(y);        if(px==py)return;        fa[py]=px;        sz[px]+=sz[py];    }    int calc(int x){        bool f=0;        int res=0;        for(int i=1;i<=n;i++)fa[i]=i,sz[i]=1;        for(int i=1;i<=s[x].sz;i++){            int a=s[x].e[i].a,b=s[x].e[i].b,c=s[x].e[i].c;            if(!same(a,b)){                res+=c;                merge(a,b);                if(sz[fa[a]]==n)f=1;            }        }        if(f)return res;        else return 0;    }    void solve(){        for(int i=1;i<=m;i++)        if(Q[i].f==1){            s[i]=s[i-1];            s[i].e[++s[i].sz]=(edge){Q[i].a,Q[i].b,i};            s[i].res=calc(i);            Pt(s[i].res);            putchar('\n');        }else if(Q[i].f==2){            s[i]=s[i-1];            s[i].sz-=Q[i].a;            s[i].res=calc(i);            Pt(s[i].res);            putchar('\n');        }else{            s[i]=s[i-2];            Pt(s[i].res);            putchar('\n');        }    }}P1;struct P2{    int fa[N],sz[N];    int getfa(int v){        if(fa[v]==v)return v;        return fa[v]=getfa(fa[v]);    }    bool same(int x,int y){        return getfa(x)==getfa(y);    }    void merge(int x,int y){        int px=getfa(x),py=getfa(y);        if(px==py)return;        fa[py]=px;        sz[px]+=sz[py];    }    void solve(){        bool f=0;        ll res=0;        for(int i=1;i<=n;i++)fa[i]=i,sz[i]=1;        for(int i=1;i<=m;i++){            if(f){                Pt(res),putchar('\n');                continue;            }            int a=Q[i].a,b=Q[i].b;            if(!same(a,b)){                res+=i;                merge(a,b);                if(sz[fa[a]]==n){                    f=1;                    Pt(res),putchar('\n');                    continue;                }            }            Pt(0),putchar('\n');        }    }}P2;struct P3{    struct edge{        int v,nxt,pre;    }e[M<<1];    struct node{        int a,b,c;    }stk[M];    int top;    int sz[N],id[N];    int head[N],edgecnt;    void add_edge(int a,int b,int c){//把b并到a上,b的原来集合为c        e[++edgecnt]=(edge){b,head[a],c};head[a]=edgecnt;sz[a]++;id[b]=a;    }    void solve(){        memset(sz,0,sizeof(sz));        memset(head,-1,sizeof(head));        top=edgecnt=0;        ll res=0;        for(int i=1;i<=n;i++)add_edge(i,i,i);        for(int i=1;i<=m;i++){            if(Q[i].f==1){                int a=id[Q[i].a],b=id[Q[i].b];                if(sz[a]<sz[b])swap(a,b);                stk[++top]=(node){a,b,i};                if(a!=b){                    for(int j=head[b];~j;j=e[j].nxt)                    add_edge(a,e[j].v,b);                    res+=i;                }            }else{                int a=Q[i].a;                while(a--){                    int a=stk[top].a,b=stk[top].b,c=stk[top].c;                    if(a!=b){                        for(int &j=head[a];e[j].pre==b;j=e[j].nxt){                            id[e[j].v]=e[j].pre;                            sz[a]--;                        }                        res-=c;                    }                    top--;                }            }            if(sz[id[1]]<n)Pt(0);            else Pt(res);            putchar('\n');        }    }}P3;struct P4{    struct edge{        int v,nxt,pre;    }e[M<<1];    struct node{        int a,b,c;        ll res;    }stk[M];    ll res;    int top;    int sz[N],id[N];    int head[N],edgecnt;    void del(int t){        while(t--){            int a=stk[top].a,b=stk[top].b,c=stk[top].c;            if(a!=b){                for(int &j=head[a];e[j].pre==b;j=e[j].nxt){                    id[e[j].v]=b;                    sz[a]--;                }                res-=c;            }            top--;        }    }    void add_edge(int a,int b,int c){//把b并到a上,b的原来集合为c        e[++edgecnt]=(edge){b,head[a],c};head[a]=edgecnt;sz[a]++;id[b]=a;    }    void solve(){        memset(sz,0,sizeof(sz));        memset(head,-1,sizeof(head));        top=res=edgecnt=0;        for(int i=1;i<=n;i++)add_edge(i,i,i);        for(int i=1;i<=m;i++){            if(Q[i].f==1){                int a=id[Q[i].a],b=id[Q[i].b];                if(sz[a]<sz[b])swap(a,b);                if(a!=b){                    for(int j=head[b];~j;j=e[j].nxt)                    add_edge(a,e[j].v,b);                    res+=i;                }                stk[++top]=(node){a,b,i,sz[id[1]]==n?res:0};            }else if(Q[i].f==2){                if(Q[i+1].f==3)top-=Q[i].a;                else del(Q[i].a);            }else{                if(Q[i-1].f==1)del(1);                else top+=Q[i-1].a;            }            Pt(stk[top].res);            putchar('\n');        }    }}P4;bool chk1(){    for(int i=1;i<=m;i++)    if(Q[i].f!=1)return false;    return true;}bool chk2(){    for(int i=1;i<=m;i++)    if(Q[i].f==3)return false;    return true;}int main(){    int a,b;    Rd(n);Rd(m);    char s[10];    for(int i=1;i<=m;i++){        scanf("%s",s);        if(s[0]=='A'){            Rd(a);Rd(b);            Q[i]=(opr){1,a,b};        }else if(s[0]=='D'){            Rd(a);            Q[i]=(opr){2,a,0};        }else Q[i]=(opr){3,0,0};    }    if(n<=1000&&m<=1000)P1.solve();    else if(chk1())P2.solve();    else if(chk2())P3.solve();    else P4.solve();    return 0;}

    还有很多小错误,需要改正!