• 2018 Multi-University Training Contest 2


    Cover

    题意:

      要求遍历一个无向图的所有边,一条边只能经过一次,问最少需要几次才能完成?最后输出每次遍历的路径。

    分析:

      感觉dls的想法是真的灵性,orz,贴一篇个人感觉写的好的博客吧,觉得大佬说的很清楚。

      参考资料:大佬博客

    代码:

    #include <bits/stdc++.h>
    
    using namespace std;
    #define ll long long
    #define ull unsigned long long
    #define cls(x) memset(x,0,sizeof(x))
    #define clslow(x) memset(x,-1,sizeof(x))
    const int maxn=1e5+100;
    
    int n,m;
    int cnt,tot;
    
    bool vis[maxn];
    int head[maxn],deg[maxn];
    vector<int>ans[maxn];
    
    struct Edge {
        bool isused;
        int v,id,nex;
    };
    Edge edge[maxn<<2];
    
    void init()
    {
        cnt=tot=0;
        cls(vis);
        cls(deg);
        clslow(head);
    }
    
    void addedge(int u,int v,int id)
    {
        edge[tot].v=v;
        edge[tot].id=id;
        edge[tot].nex=head[u];
        edge[tot].isused=false;
        head[u]=tot++;
    }
    
    void dfs(int u)
    {
        vis[u]=true;
        for(int i=head[u];i!=-1;i=edge[i].nex){
            if(edge[i].isused)  continue;
            int v=edge[i].v,id=edge[i].id;
            edge[i].isused=edge[i^1].isused=true;
    
            dfs(v);
            if(id)  ans[cnt].push_back(-id);
            else    cnt++;
        }
    }
    
    void print()
    {
        printf("%d
    ",cnt);
        for(int i=1;i<=cnt;i++){
            int sz=ans[i].size();
            printf("%d",sz);
            for(int j=0;j<sz;j++){
                printf(" %d",ans[i][j]);
            }
            printf("
    ");
            ans[i].clear();
        }
    }
    
    int main()
    {
    //    freopen("in.txt","r",stdin);
        while(scanf("%d%d",&n,&m)!=EOF)
        {
            init();
            for(int i=1;i<=m;i++){
                int u,v;
                scanf("%d%d",&u,&v);
                deg[u]++;deg[v]++;
                addedge(u,v,i);addedge(v,u,-i);
            }
    
            int last=-1;
            for(int i=1;i<=n;i++){
                if(deg[i]&1){
                    if(last==-1)    last=i;
                    else{
                        addedge(last,i,0);
                        addedge(i,last,0);
                        last=-1;
                    }
                }
            }
            for(int i=1;i<=n;i++){
                if(!vis[i]&&(deg[i]&1)){
                    cnt++;
                    dfs(i);
                    cnt--;
                }
            }
            for(int i=1;i<=n;i++){
                if(!vis[i]&&deg[i]){
                    cnt++;
                    dfs(i);
                }
            }
    
            print();
        }
        return 0;
    }
    View Code

    Game

    题意:

      Alice和Bob先后手玩游戏,每次可以从一个集合中拿出一个数(初始为1~n),拿出之后,这个数的所有因数都会从这个集合中消失,谁没有数可拿时,就输了。问Alice是否能赢?

    分析:

      如果先手拿x为必胜态,那么Alice一定能赢;如果先手拿x为必输态,那么Alice先手拿1,则必输态转移给Bob,所以Alice一定能赢。

    代码:

    #include <cmath>
    #include <vector>
    #include <stdio.h>
    #include <iostream>
    
    using namespace std;
    #define ll long long
    const int maxn=1e5+100;
    
    int main()
    {
    //    freopen("in.txt","r",stdin);
        int n;
        while(scanf("%d",&n)!=EOF)
        {
            printf("Yes
    ");
        }
        return 0;
    }
    View Code

    Hack It

    题意:

      要求构造一个n*n的矩阵,要求不存在一个子矩阵四个角都为1,矩阵的大小1<=n<=2000,矩阵中1的个数大于等于85000。

    分析:

      n=3,1的位置在j上依次+0,1,2,3……

      

      对于上图,我们首先看一下,上面的矩阵是怎么构造出来的。

      1.首先对于第i块,第一列1的位置为第i个。

      2.对于第i块,我们也可以看成有n个小块,对于第i块中的第j小块(j>1),它的第k行中1的位置是由前一小块中1的位置+(k-1)得到,也就是说由第一小块中1的位置+(k-1)*(j-1)。

      因为矩阵最大为2000,所以我们构造矩阵的大小sz就取2000,为了构造时不会重复,所以n采用质数,最接近sqrt(sz)=sqrt(2000)的质数为47.

    代码:

    #include <bits/stdc++.h>
    
    using namespace std;
    #define ll long long
    #define ull unsigned long long
    #define cls(x) memset(x,0,sizeof(x))
    #define clslow(x) memset(x,-1,sizeof(x))
    const int maxn=3000;
    
    int n=47,sz=2000;
    
    bool a[maxn][maxn];
    
    int main()
    {
    //    freopen("in.txt","r",stdin);
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                for(int k=0;k<n;k++){
                    //i*n+j:第i+1块第j+1行
                    //k*n:第k+1小块
                    //(j*k+i)%n:第(j*k+i)%n列
                    a[i*n+j][k*n+(j*k+i)%n]=1;
                }
            }
        }
        printf("%d
    ",sz);
        for(int i=0;i<sz;i++){
            for(int j=0;j<sz;j++){
                printf("%d",a[i][j]);
            }
            printf("
    ");
        }
        return 0;
    }
    View Code

    Matrix

    题意:

      给出n*m的矩阵,每个格子可以涂白色和黑色,现在问至少有row行col列为黑色的涂色方案共有多少种?

    分析:

      很容易看出这是一个容斥原理的题,然后……还是乖乖看dls吹水吧。对于这题总体思路是,分别求出行和列的容斥系数(我是这么理解的),然后最后一个容斥公式累加得到答案,具体分析看代码注释吧。

    代码:

    #include <bits/stdc++.h>
    
    using namespace std;
    #define ll long long
    #define ull unsigned long long
    #define cls(x) memset(x,0,sizeof(x))
    #define clslow(x) memset(x,-1,sizeof(x))
    
    const int maxn=3000+100;
    const int mod=998244353;
    
    int n,m,row,col;
    
    int x[maxn*maxn],c[maxn][maxn];
    int a[maxn],b[maxn],f[maxn][maxn];
    
    void init()
    {
        //组合数
        for(int i=0;i<maxn;i++){
            c[i][0]=1;c[i][i]=1;
            for(int j=1;j<i;j++){
                c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
            }
        }
        //2的幂
        x[0]=1;
        for(int i=1;i<maxn*maxn;i++){
            x[i]=(x[i-1]<<1)%mod;
        }
    }
    
    int main()
    {
    //    freopen("in.txt","r",stdin);
        init();
        while(scanf("%d%d",&n,&m)!=EOF)
        {
            scanf("%d%d",&row,&col);
    
            //a[i]:我把它理解为容斥系数。
            //我们知道容斥原理是由单个集合的大小减去两个集合的交集,加上三个集合的交集……
            //这里也是类似,因为最小为i行,所以i行对应单个集合,它的容斥系数就为1。当行数为i+1时,
            //因为下面计算f[k][l]时空白格子是随机染色,所以行数低的与行数高的有重复的染色方案,
            //根据容斥原理我们需要减去任意两个集合的交集部分,所以对应的容斥系数减去交集的个数。
            a[row]=1;
            for(int i=row+1;i<=n;i++){
                a[i]=1;
                for(int j=row;j<i;j++){
                    a[i]=(a[i]-(ll)a[j]*c[i][j])%mod;
                }
            }
            //含义同上
            b[col]=1;
            for(int i=col+1;i<=m;i++){
                b[i]=1;
                for(int j=col;j<i;j++){
                    b[i]=(b[i]-(ll)b[j]*c[i][j])%mod;
                }
            }
            //不同的行列的方法数之间的涂色方案有重复
            //有k行l列全为黑色的涂色方案
            for(int i=row;i<=n;i++){
                for(int j=col;j<=m;j++){
                    //涂黑k行l列后,剩下(n-k)*(m-l)格子可以为黑可以为白,
                    //每个格子有2种可能,总共有2^((n-k)*(m-l))
                    f[i][j]=(ll)c[n][i]*c[m][j]%mod*x[(n-i)*(m-j)]%mod;
                }
            }
    
            ll ans=0;
            for(int i=row;i<=n;i++){
                for(int j=col;j<=m;j++){
                    ////容斥原理计算出所有的可能性
                    ans=(ans+(ll)a[i]*b[j]%mod*f[i][j])%mod;
                }
            }
            //+mod防止负数
            ans=(ans+mod)%mod;
            printf("%lld
    ",ans);
        }
        return 0;
    }
    View Code

    Naive Operations

    题意:

      给出b数组,和q次操作。如果为add l r操作,则把a数组【l,r】区间的值都加上一,query l r操作询问【l,r】区间a【i】/b【i】(向下取整)的和。

    分析:

      利用线段树存下区间的最大a[i]和最小b[i]值,当a[i]>=b[i]时,说明c[i]的值可能需要加一(c[i]=a[i]/b[i]),为什么说是可能呢?因为如果两个数的a[i],b[i]值分别为(1,2),(2,3),这时虽然线段树没有更新,但是它会往下去搜索,就会导致时间的浪费,sum存储对应区间的c[i]之和,具体看代码注释。

    代码:

    #include <cmath>
    #include <vector>
    #include <stdio.h>
    #include <iostream>
    
    using namespace std;
    #define ll long long
    #define lson l,m,rt<<1
    #define rson m+1,r,rt<<1|1
    const int maxn=1e5+100;
    
    int n,q,l,r,c;
    
    char s[10];
    
    ll b[maxn];
    //add[rt]:延迟标记,rt区间的a[i]值需要加上add[rt]
    //sum[rt]:ai/bi的区间和
    //minval[rt]:rt区间的最小b[i]值
    //maxval[rt]:rt区间的最大a[i]值
    ll sum[maxn<<2],add[maxn<<2],minval[maxn<<2],maxval[maxn<<2];
    
    void PushUp(int rt)
    {
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
        minval[rt]=min(minval[rt<<1],minval[rt<<1|1]);
        maxval[rt]=max(maxval[rt<<1],maxval[rt<<1|1]);
    }
    
    void PushDown(int rt)
    {
        if(add[rt]){
            add[rt<<1]+=add[rt];
            add[rt<<1|1]+=add[rt];
            maxval[rt<<1]+=add[rt];
            maxval[rt<<1|1]+=add[rt];
            add[rt]=0;
        }
    }
    
    void build(int l,int r,int rt)
    {
        add[rt]=0;
        if(l==r){
            sum[rt]=0;
            maxval[rt]=0;
            minval[rt]=b[l];
            return;
        }
        int m=(l+r)>>1;
        build(lson);
        build(rson);
        PushUp(rt);
    }
    
    void update(int L,int R,int c,int l,int r,int rt)
    {
        if(L<=l&&r<=R){
            maxval[rt]+=c;
            if(maxval[rt]<minval[rt]){
                add[rt]+=c;
                return;
            }
            if(l==r&&maxval[rt]>=minval[rt]){
                sum[rt]++;
                //第i次贡献时,maxval[rt]的值为i*b[l]
                //所以每次maxval[rt]贡献后,minval[rt]的值需要加上b[l]
                minval[rt]+=b[l];
                return;
            }
        }
        PushDown(rt);
        int m=(l+r)>>1;
        if(L<=m)    update(L,R,c,lson);
        if(R>m)     update(L,R,c,rson);
        PushUp(rt);
    }
    
    ll query(int L,int R,int l,int r,int rt)
    {
        if(L<=l&&r<=R){
            return sum[rt];
        }
        PushDown(rt);
        ll ans=0;
        int m=(l+r)>>1;
        if(L<=m) ans+=query(L,R,lson);
        if(R>m)  ans+=query(L,R,rson);
        return ans;
    }
    
    void debug(int l,int r,int rt)
    {
        if(l==r){
            printf("debug:%d %d
    ",l,sum[rt]);
            return;
        }
        int m=(l+r)>>1;
        debug(lson);
        debug(rson);
    }
    
    int main()
    {
    //    freopen("in.txt","r",stdin);
        while(scanf("%d%d",&n,&q)!=EOF)
        {
            for(int i=1;i<=n;i++){
                scanf("%lld",&b[i]);
            }
    
            build(1,n,1);
            for(int j=1;j<=q;j++){
                scanf("%s%d%d",s,&l,&r);
                if(s[0]=='a'){
                    update(l,r,1,1,n,1);
    //                debug(1,n,1);
                } else {
                    printf("%lld
    ",query(l,r,1,n,1));
                }
            }
        }
        return 0;
    }
    View Code

      对于上面存在的无效的操作,还有一种办法就是,线段树维护最小b[i]值,每当对区间的a[i]值进行+1处理时,我们就把相应区间的b[i]值进行减1,直到存在b[i]值为0,说明这时某些节点a[i]/b[i]值==1,于是我们把这些节点的sum数组+1,同时赋b[rt]重新为初始的b[i]值。可以看下两次所跑的时间,第二次跑的时间明显少了很多。

    代码:

    #include <cmath>
    #include <vector>
    #include <stdio.h>
    #include <iostream>
    
    using namespace std;
    #define ll long long
    #define lson l,m,rt<<1
    #define rson m+1,r,rt<<1|1
    const int maxn=1e5+100;
    int n,q,l,r,c;
    char s[10];
    
    ll b[maxn];
    ll sum[maxn<<2],add[maxn<<2],minval[maxn<<2];
    
    void PushUp(int rt)
    {
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
        minval[rt]=min(minval[rt<<1],minval[rt<<1|1]);
    }
    
    void PushDown(int rt)
    {
        if(add[rt]){
            add[rt<<1]+=add[rt];
            add[rt<<1|1]+=add[rt];
            minval[rt<<1]-=add[rt];
            minval[rt<<1|1]-=add[rt];
            add[rt]=0;
        }
    }
    
    void build(int l,int r,int rt)
    {
        add[rt]=0;
        if(l==r){
            sum[rt]=0;
            minval[rt]=b[l];
            return;
        }
        int m=(l+r)>>1;
        build(lson);
        build(rson);
        PushUp(rt);
    }
    
    void update(int L,int R,int c,int l,int r,int rt)
    {
        if(L<=l&&r<=R){
            minval[rt]-=c;
            if(minval[rt]>0){
                add[rt]+=c;
                return;
            }
            if(l==r&&minval[rt]<=0){
                sum[rt]++;
                minval[rt]+=b[l];
                return;
            }
        }
        PushDown(rt);
        int m=(l+r)>>1;
        if(L<=m)    update(L,R,c,lson);
        if(R>m)     update(L,R,c,rson);
        PushUp(rt);
    }
    
    ll query(int L,int R,int l,int r,int rt)
    {
        if(L<=l&&r<=R){
            return sum[rt];
        }
        PushDown(rt);
        ll ans=0;
        int m=(l+r)>>1;
        if(L<=m) ans+=query(L,R,lson);
        if(R>m)  ans+=query(L,R,rson);
        return ans;
    }
    
    void debug(int l,int r,int rt)
    {
        if(l==r){
            printf("debug:%d %d
    ",l,sum[rt]);
            return;
        }
        int m=(l+r)>>1;
        debug(lson);
        debug(rson);
    }
    
    int main()
    {
    //    freopen("in.txt","r",stdin);
        while(scanf("%d%d",&n,&q)!=EOF)
        {
            for(int i=1;i<=n;i++){
                scanf("%lld",&b[i]);
            }
    
            build(1,n,1);
            for(int j=1;j<=q;j++){
                scanf("%s%d%d",s,&l,&r);
                if(s[0]=='a'){
                    update(l,r,1,1,n,1);
    //                debug(1,n,1);
                } else {
                    printf("%lld
    ",query(l,r,1,n,1));
                }
            }
        }
        return 0;
    }
    View Code

    Swaps and Inversions

    题意:

      给出一个序列,如果存在一对逆序对需要花费x元,现在可以对两个相邻的元素进行交换,每次交换需要花费y元,问最少要花费多少钱?

    分析:

      首先一次交换能够减少1对逆序对,那么num对逆序对,就需要num次交换。如果交换i次,花费cost=i*y+(num-i)*x=num*x-i*(y-x),不难看出,mincost=min(x,y)*num。

    代码:

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    #define ll long long
    #define lson l,m,rt<<1
    #define rson m+1,r,rt<<1|1
    const int maxn=1e5+100;
    int sum[maxn<<2],x[maxn],Hash[maxn];
    
    void PushUp(int rt)
    {
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    }
    
    void build(int l,int r,int rt)
    {
        sum[rt]=0;
        if(l==r){
            return;
        }
        int m=(l+r)>>1;
        build(lson);
        build(rson);
    }
    
    void update(int p,int add,int l,int r,int rt)
    {
        if(l==r){
            sum[rt]+=add;
            return;
        }
        int m=(l+r)>>1;
        if(p<=m)    update(p,add,lson);
        else        update(p,add,rson);
        PushUp(rt);
    }
    
    int query(int L,int R,int l,int r,int rt)
    {
        if(L<=l&&r<=R){
            return sum[rt];
        }
        int ans=0;
        int m=(l+r)>>1;
        if(L<=m)    ans+=query(L,R,lson);
        if(R>m)     ans+=query(L,R,rson);
        return ans;
    }
    
    int main()
    {
    //    freopen("in.txt","r",stdin);
        int n,costx,costy;
        while(scanf("%d%d%d",&n,&costx,&costy)!=EOF)
        {
            ll cntx=0,cnty=0;
            build(0,n-1,1);
            for(int i=0;i<n;i++){
                scanf("%d",&x[i]);
                Hash[i]=x[i];
            }
    
            sort(Hash,Hash+n);
            int sz=unique(Hash,Hash+n)-Hash;
            for(int i=0;i<n;i++){
                x[i]=lower_bound(Hash,Hash+sz,x[i])-Hash;
            }
    
            for(int i=0;i<n;i++){
                cntx+=query(x[i]+1,n-1,0,n-1,1);
                update(x[i],1,0,n-1,1);
            }
            cnty=cntx;
    
            ll ans=min(cntx*costx,cnty*costy);
            printf("%lld
    ",ans);
        }
        return 0;
    }
    View Code
  • 相关阅读:
    FineBI与power BI,一个是国外风生水起的微软巨头,一个是方兴未艾的国产BI厂商领导者
    Linux下的crontab定时执行任务命令详解举例
    手把手教你搭建SSH框架(Eclipse版)
    一、连接池的定义
    centos安装sftp服务win搭建 sftp 服务器
    前往阿里云的企业优惠活动页面
    世界可能是思想最为混乱的时候,无论你说什么
    python面向对象编程class1
    Python 文件I/O 文件读写模式r,r+,w,w+,a,a+的区别
    Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都在那里
  • 原文地址:https://www.cnblogs.com/shutdown113/p/9371135.html
Copyright © 2020-2023  润新知