zoukankan      html  css  js  c++  java
  • 第十五届中北大学算法与程序设计竞赛

    比赛地址:传过去送的门

    好久没写博客了,没想到突然间就松懈了那么久,得紧起来了。

    之前的随笔里的代码因为设置了行内标号,现在复制代码前面都带上了行号,而且没法点击复制,哪位能人异士,信男信女懂怎么觉得,please help help me。

    话不多说,水了场比赛,再来水个题解。比赛时就做了9个,DJLM以及正经写法等补了再补上。

    A.俄罗斯方块

    上来先开的这个,好家伙,带图的,应该挺难。然后看玩题目,好吧,直接模拟就好,从最上面判断到下面,找到当前方块的底层,然后把它放上就行。

    #include<bits/stdc++.h>
    using namespace std;
    int mp[21][21];
    int main(){
        int n,op,x;
        scanf("%d",&n);
        for(int i=0;i<=10;i++) mp[0][i]=1;
        while(n--){
            scanf("%d%d",&op,&x);
            if(op==1){
                for(int i=10;i>=1;i--){
                    if(mp[i-1][x]||mp[i-1][x+1]){
                        mp[i][x]=mp[i][x+1]=mp[i+1][x]=mp[i+1][x+1]=1;
                        break;
                    }
                }
            }else if(op==2){
                for(int i=10;i>=1;i--){
                    if(mp[i-1][x]||mp[i-1][x+1]||mp[i-1][x+2]){
                        mp[i][x]=mp[i][x+1]=mp[i][x+2]=mp[i+1][x]=1;
                        break;
                    }
                }
            }else if(op==3){
                for(int i=10;i>=1;i--){
                    if(mp[i-1][x]||mp[i-1][x+1]||mp[i-1][x+2]||mp[i-1][x+3]){
                        mp[i][x]=mp[i][x+1]=mp[i][x+2]=mp[i][x+3]=1;
                        break;
                    }
                }
            }else{
                for(int i=10;i>=1;i--){
                    if(mp[i-1][x]||mp[i-1][x+1]||mp[i-1][x+2]){
                        mp[i][x]=mp[i][x+1]=mp[i][x+2]=mp[i+1][x+1]=1;
                        break;
                    }
                }
            }
        }
        for(int i=10;i>=1;i--)
            for(int j=1;j<=10;j++) printf("%d%c",mp[i][j]," 
    "[j==10]);
        return 0;
    } 
    哦,毛子

     B.真的是签到题

    真的就是签到题。

    K.签个到

    贪心。贪心往往是种直觉,很难解释里面的哲学。

    我这里瞎比比一下我的想法,我们假设,修改之后最大值跟最小值的位置是x,y,那么未修改的差值就是a[x]-a[y],接下来每次修改便是使得差值增大x或者增大y。

    我们要差值最大,所以m次修改自然是都作用在下标最大的位置最好。所以先求出,a[i]+m*i中的最大值和a[i]-m*i中的最小值,然后再分别与原a[i]做差,取绝对值最大值。

    还有注意n==1的时候。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll; 
    const int N=1e5+11;
    ll a[N];
    int main(){
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
        ll mx=a[1]+m,mi=a[1]-m,ans=0;
        for(int i=2;i<=n;i++){
            mx=max(mx,a[i]+1ll*i*m);
            mi=min(mi,a[i]-1ll*i*m);
        }
        for(int i=1;i<=n;i++){
            ans=max(ans,abs(mx-a[i]));
            ans=max(ans,abs(mi-a[i]));
        }
        if(n==1) printf("0
    ");
        else printf("%lld
    ",ans);
        return 0;
    } 
    你也是个贪心的人吗

    E.简单的线性代数

    说到线代就得想起军波,好吧军波老师跟这题没关。

    因为操作的都是n阶方阵,那他们之间的乘除其实也就跟数的乘除差不多,都满足结合律啊等等的。

    设B=E+A+..+Ak-1  ,根据等比数列求和公式就有B=E(E-Ak)/(E-A)。

    而求的是B的逆,也就是B-1,那就是E-A=E(E-Ak)B-1

    题目又说了Ak=0.所以B-1=E-A,也就是A矩阵主对角线上的数被1减去,其余的被0减去。(看样例输出,也可以猜一发,搏一搏,转眼就开学。)

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=1011;
    ll a[N][N];
    int main(){
        int n;
        ll k;
        scanf("%d%lld",&n,&k);
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++) scanf("%lld",&a[i][j]);
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++){
                if(i==j) a[i][j]=1ll-a[i][j];
                else a[i][j]*=-1ll;
                printf("%lld%c",a[i][j]," 
    "[j==n-1]);
            }
        return 0;
    }
    军波教线代

     G.数位操作1

    这题应该算个模拟题?,首先啊,ans>9,那么对于n<=9的答案肯定是10+n。

    对于n>10的呢,因为要把n拆成一堆<=9的数的乘积,那如果n包含了>9的质因子,此时肯定表示不粗来了,就是-1。

    对于可以表示出来的n,我们就把它里面的2,3,5,7拆出来,考虑到要ans最小,首先肯定要先保证数位最少。

    5跟7的话,肯定占一个数位了,而3个2可以合成一个8只占1个数位,而2个3可以合成一个9只占一个数位。

    然后我们需要对多出来的2跟3,另外讨论,如果两个2,一个3的话,此时2可以合成4占一个数位,3也要单独占一个数位,但我们可以把4中的一个2给3,这时26比34要小。

    1个2和1个3话,他们可以合成6只占一个数位。

    再多出来的2,3就直接是2,3了,最后我们再把所有数排序就好了。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const ll a[11]={2,3,5,7};
    int cnt[11],ans[111];
    int main(){
        ll n;
        while(~scanf("%lld",&n)){
            if(n<10){
                printf("%lld
    ",10+n);
                continue;
            }
            for(int i=0;i<4;i++){
                cnt[i]=0;
                while(n%a[i]==0){
                    n/=a[i];
                    cnt[i]++;
                }
            }
            if(n>1) printf("-1
    ");
            else{
                int pn=0;
                while(cnt[0]>=3){
                    ans[pn++]=8;
                    cnt[0]-=3;
                }
                while(cnt[1]>=2){
                    ans[pn++]=9;
                    cnt[1]-=2;
                }
                if(cnt[0]==2){
                    if(cnt[1]){
                        cnt[1]--;
                        ans[pn++]=2;
                        ans[pn++]=6;
                    }else ans[pn++]=4;
                }                
                else if(cnt[0]==1){
                    if(cnt[1]){
                        cnt[1]--;
                        ans[pn++]=6;
                    }else ans[pn++]=2;
                }
                if(cnt[1]==1) ans[pn++]=3;
                while(cnt[2]){
                    ans[pn++]=5;
                    cnt[2]--;
                }
                while(cnt[3]){
                    ans[pn++]=7;
                    cnt[3]--;
                }
                sort(ans,ans+pn);
                for(int i=0;i<pn;i++) printf("%d",ans[i]);
                printf("
    ");
            }
        }
        return 0;
    }
    这是3张同样的能升星?

     C.港口

    跟着榜做,看这题才十几个人过,以为是很难的题,点开一看,似曾相似燕归来?

    这是道原题,具体题号忘了,记得那题还需要求最小操作下有多少相同的种数。

    回到这题,这题的思想是差分,不知道差分是什么的小问号们可以看我的差分系列博客(绝对没有骗点击量)。

    我们求出差分数组dif的话,根据题意,我们就是要让dif都为0,此时就是大家都一样了。

    而一段区间[l,r]内加上一个数a的话,便是在差分数组上的体现就是dif[l]+a,dif[r+1]-a。

    所以,我们要最少操作次数的话,每次肯定是选取dif[l]跟dif[r]一正一负的来进行一个操作。

    而多次来的那些正的或者是负的dif,则让每次操作l=0,或者r=n+1,也就是从开始到这个位置,或者这个位置到结尾的区间都加上a即可。

    这题里a是1,所以答案就是max(所有正的dif和,所有负的dif的绝对值和)。

    而多说一点,相同的种数的话便是所有dif的总和的绝对值+1,因为1个是最终相同的,而它可以更改的范围就是多出来的dif进行搭配,由此不改变方案数。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+11;
    typedef long long ll;
    ll a[N];
    int main(){
        int n;
        scanf("%d",&n);
        for(int i=0;i<n;i++) scanf("%lld",&a[i]);
        ll ans1=0,ans2=0;
        for(int i=1;i<n;i++){
            if(a[i]>=a[i-1]) ans1+=a[i]-a[i-1];
            else ans2-=a[i]-a[i-1];
        }
        printf("%lld
    ",max(ans1,ans2));
        return 0;
    } 
    就是这么短

     F.集合操作.

    也是跟着榜做,看这题才几个人过,以为是很难的题。但想了会,set瞎搞就行了。

    如果,我们有个答案数组存了所有的数,当1操作其实是在这个数组中删去x,而2操作是加上x,而3操作的话就是二分找到第一个大于等于x的数。
    但x<1000000000,直接存所有数是不可能的。而想一下,每个x,如果它不在题目的S集合中,那么答案肯定就是x,否则就得看前面有没有往S中添加过x+1。

    而添加过x+1的话,就还得看x+2,一直到一个没有操作中出现过的数,而这个数肯定是操作出现过的某个数+1。

    所有对于每次操作,我们把x跟x+1添加进我们前面所说的答案数组中,这个数组的也就2*n而已了。

    为了方便直接用set来实现这个答案数组即可。因为set操作大概是log的,所以nlogn时间复杂度上也可以。

    #include<bits/stdc++.h>
    #include<tr1/unordered_map>
    using namespace std;
    const int N=1e6+11;
    int op[N],x[N];
    set<int> se;
    int main(){
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d%d",&op[i],&x[i]);
            se.insert(x[i]);
            se.insert(x[i]+1);
        }
        for(int i=1;i<=n;i++){
            if(op[i]==1){
                if(se.count(x[i])) se.erase(x[i]);
            }else if(op[i]==2) se.insert(x[i]);
            else{
                set<int>::iterator it=se.lower_bound(x[i]);
                int ans=*it;
                printf("%d
    ",ans);
            }
        }
        return 0;
    }
    还是这么短

     H.数位操作2

    看上去像个数位dp。刚想往那里想一下,突然想到直接递推dp可不可以呢。

    dp[i][sum][p1][p2]即为前i个位置,数的和为sum,倒数第二个数位p1,倒数第一个数位p2的方案数。

    转移的话,假设下一个数为p3,先判断(p1*100+p2*10+p3)%x等不等于0,然后便是

    dp[i+1][sum+p3][p2][p3]+=dp[i][sum][p1][p2]

    这样需要遍历i,然后枚举sum,以及p1,p2,p3,时间复杂度大概是50*450*9*9*9=‭18,225,000‬,1e7级别的,而2秒刚好可以。

    #include<bits/stdc++.h>
    #include<tr1/unordered_map>
    using namespace std;
    const int N=55,M=511,md=1000009;
    int dp[N][M][11][11]; 
    int main(){
        int n,sum,x;
        scanf("%d%d%d",&n,&sum,&x);
        if(sum>450){
            printf("0
    ");
            return 0;
        }
    //    int res=0;
    //    for(int i=0;i<=9;i++)
    //        for(int j=0;j<=9;j++)
    //            for(int k=0;k<=9;k++){
    //                if(i+j+k==sum&&(i*100+j*10+k)%x==0) res++;
    //            }
    //    printf("%d
    ",res);
        for(int i=0;i<=9;i++)
            for(int j=0;j<=9;j++) dp[2][i+j][i][j]=1;
        for(int i=2;i<n;i++){
            int lim=min(9*i,sum);
            for(int j=0;j<=lim;j++)
                for(int k=0;k<=9;k++)
                    for(int l=0;l<=9;l++)
                        for(int m=0;m<=9;m++){
                            if((k*100+l*10+m)%x!=0) continue;
                            dp[i+1][j+m][l][m]=(dp[i+1][j+m][l][m]+dp[i][j][k][l])%md;
                        }
        }
        int ans=0;
        for(int i=0;i<=9;i++)
            for(int j=0;j<=9;j++){
                ans=(ans+dp[n][sum][i][j])%md;
            }
        printf("%d
    ",ans);
        return 0;
    } 
    超过30就是长的

     I.配对

    图论,好久没做了。最大对数不用说了,每个奇数都可跟每个偶数配对,也就是两者数目中的最小值。

    那把奇数分一边,偶数分一边,老二分图了,价值和也就是要求二分图最大权匹配?km算法都快忘了(其实已经忘了。)

    看数据量比较小,所以我尝试的是最小费用最大流的做法。

    我们用个源点跟所有奇数建边,流量为1,费用为0,而所有偶数跟汇点建边,流量为1,费用为0。而奇数与偶数建边,流量为1,费用变为负的两者的异或值。

    建好边,跑一遍最小费用最大流完事,为了避免超时,我用的是势函数的dijk的版本的费用流。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=611;
    const ll inf=1e16+7;
    struct Side{
        int v,ne,w,val;
        Side(){}
        Side(int v,int val):v(v),val(val){}
        bool operator<(const Side &s1)const{
            return val>s1.val;
        }
    }S[N*N];
    bool vis[N];
    int sb,se,sn,a[N],head[N],lu[N],h[N];
    ll dis[N],flow[N];
    void init(int n){
        sn=0;
        for(int i=0;i<=n;i++) head[i]=-1;
    }
    void add(int u,int v,int w,int val){
        S[sn].w=w;S[sn].val=val;
        S[sn].v=v;S[sn].ne=head[u];
        head[u]=sn++;
    }
    void addE(int u,int v,int w,int val){
        add(u,v,w,val);add(v,u,0,-val); 
    }
    bool dijk(int n){
        priority_queue<Side> q;
        for(int i=0;i<=n;i++){
            lu[i]=-1;    
            dis[i]=flow[i]=inf;
        }
        dis[sb]=0;
        q.push(Side(sb,dis[sb]));
        while(!q.empty()){
            int u=q.top().v,w=q.top().val;q.pop();
            if(dis[u]<w) continue;
            for(int i=head[u];~i;i=S[i].ne){
                int v=S[i].v;
                if(S[i].w>0&&dis[v]>dis[u]+S[i].val+h[u]-h[v]){
                    lu[v]=i;
                    dis[v]=dis[u]+S[i].val+h[u]-h[v];
                    flow[v]=min(flow[u],1ll*S[i].w);
                    q.push(Side(v,dis[v]));
                }
            }
        }
        return dis[se]!=inf; 
    }
    int mfml(int n){
        int ans=0;
        ll ansc=0;
        for(int i=0;i<=n;i++) h[i]=0;
        while(dijk(n)){
            ans+=flow[se];
            ansc+=flow[se]*(dis[se]+h[se]-h[sb]);
            for(int i=lu[se];~i;i=lu[S[i^1].v]){
                S[i].w-=flow[se];
                S[i^1].w+=flow[se];
            }
            for(int i=0;i<=n;i++) h[i]+=dis[i];
        }
        printf("%d %lld
    ",ans,-ansc); 
        return 0;
    }
    int main(){
        int n;
        scanf("%d",&n);
        sb=0;se=n+1;
        init(n+1);
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
            if(a[i]&1) addE(sb,i,1,0);
            else addE(i,se,1,0);
            for(int j=1;j<i;j++){
                if((a[i]+a[j])&1){
                    if(a[i]&1) addE(i,j,1,-(a[i]^a[j]));
                    else addE(j,i,1,-(a[i]^a[j]));
                }
            }
        }
        mfml(n+1);
        return 0;
    }
    我还能更长

     这回我没咕咕咕。

    D.构造数组

    这题也不是什么难题,就直接从后往前看,如果当前这个数要插入的位置是pos,那么它真实位置肯定是要大于等于pos的,因为在它后面的数插在比pos小的位置,会把它往后挤。

    所以就是用个线段树来维护[1,n]区间每个位置有没有数,然后就查询第一个存在着pos个空位的位置。

    #include<bits/stdc++.h>
    #define L(x) (x<<1)
    #define R(x) (x<<1|1)
    #define M(x) ((T[x].l+T[x].r)>>1)
    using namespace std;
    const int N=5e5+11;
    struct Node{
        int l,r,pos;
    }T[N<<2];
    int ind[N],num[N],ans[N];
    void build(int x,int l,int r){
        T[x].l=l;T[x].r=r;
        if(l==r){
            T[x].pos=1;
            return ;
        }
        build(L(x),l,M(x));
        build(R(x),M(x)+1,r);
        T[x].pos=T[L(x)].pos+T[R(x)].pos;
    }
    int modify(int x,int y){
        if(T[x].l==T[x].r){
            T[x].pos=0;
            return T[x].l;
        }
        int ans;
        if(T[L(x)].pos>=y) ans=modify(L(x),y);
        else ans=modify(R(x),y-T[L(x)].pos);
        T[x].pos=T[L(x)].pos+T[R(x)].pos;;
        return ans;
    }
    int main(){
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d%d",&ind[i],&num[i]);
        build(1,1,n);
        for(int i=n;i>=1;i--){
            int p=modify(1,ind[i]);
            ans[p]=num[i];
        }
        for(int i=1;i<=n;i++) printf("%d%c",ans[i]," 
    "[i==n]);
        return 0;
    }
    线段树哟

    M.降维打击

    每次都可以上下左右随意走的话,总的方案数是4k,那么就需要找到能走到边界的方案数。

    这里就可以以走的次数来进行dp递推,或者是bfs来进行。我这里是写bfs,dp[x][y][z]就是到x,y位置时已经走了z步的方案数。因为队列里是肯定是按走的步数少的在前面的,所以每个位置同样的z只进队一次即可。

    bfs完了之后,我们就统计边界位置能在0~k内走到的方案数。注意的是,如果这个边界位置在kk步就已经走到了,那么剩下的4k-kk的方案数也算是走到了边界的。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=131,md=1000000007;
    const int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
    struct Node{
        int x,y,z;
        Node(){}
        Node(int x,int y,int z):x(x),y(y),z(z){}
    }; 
    queue<Node> q;
    int n,m,k,x,y,dp[N][N][N*2];
    bool mp[N][N],vis[N][N][N*2];
    void bfs(){
        dp[x][y][0]=1;
        vis[x][y][0]=1;
        q.push(Node(x,y,0));
        while(!q.empty()){
            Node p=q.front();
            q.pop();
            if(p.z==k) continue;
            for(int i=0;i<4;i++){
                int dx=p.x+dir[i][0];
                int dy=p.y+dir[i][1];
                if(dx<0||dx>n+1||dy<0||dy>m+1) continue;
                if(mp[dx][dy]) continue;
                dp[dx][dy][p.z+1]+=dp[p.x][p.y][p.z];
                if(dp[dx][dy][p.z+1]>=md) dp[dx][dy][p.z+1]-=md;
                if(dx<1||dx>n||dy<1||dy>m) continue;
                if(vis[dx][dy][p.z+1]) continue;
                vis[dx][dy][p.z+1]=1;
                q.push(Node(dx,dy,p.z+1));
            }
        }
    }
    int poww(int a,int b){
        int ans=1;
        while(b){
            if(b&1) ans=1ll*ans*a%md;
            a=1ll*a*a%md;
            b>>=1;
        }
        return ans;
    }
    int main(){
        scanf("%d%d%d%d%d",&n,&m,&x,&y,&k);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++) scanf("%d",&mp[i][j]);
        bfs();
        int fz=0,fm=poww(4,k);
        for(int kk=0;kk<=k;kk++){
            for(int i=0;i<=n+1;i++){
                fz+=1ll*dp[i][0][kk]*poww(4,k-kk)%md;
                if(fz>=md) fz-=md;
                fz+=1ll*dp[i][m+1][kk]*poww(4,k-kk)%md;
                if(fz>=md) fz-=md;
            }
        }
        for(int kk=0;kk<=k;kk++){
            for(int i=1;i<=m;i++){
                fz+=1ll*dp[0][i][kk]*poww(4,k-kk)%md;
                if(fz>=md) fz-=md;
                fz+=1ll*dp[n+1][i][kk]*poww(4,k-kk)%md;
                if(fz>=md) fz-=md;
            }
        }
        fm=poww(fm,md-2);
        fz=1ll*fz*fm%md;
        printf("%d
    ",fz);
        return 0;
    }
    上上下下左左右右

    L.20200524

    一看就是容斥了,可我tm数学不好。。。

    我们先把20200524的因子提出出来,接下来就是它们之间的匹配。比如x中2的倍数,跟y中20200524/2的倍数进行配对。

    但是像x中2的倍数与y中20200524/2的倍数进行配对,然后x中6的倍数与y中20200524/6的倍数进行配对时,前者会包含后者的方案数,产生了重复。

    因为2的倍数中包含了6的倍数,所以算x中2的倍数与y中20200524/2的倍数时,应该去掉x/中6的倍数与y中20200524/2的倍数进行配对的方案数。

    那么我们先预处理出,每个20200524的因子在x中的倍数的个数,然后就可以根据容斥,在某个因子的方案数中把该因子的倍数的方案数去掉。最后再与y中的进行匹对。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int n=20200524,m=24;
    const int a[31]={1,2,3,4,6,12,1123,1499,2246,2998,3369,4492,4497,5996,6738,8994,13476,
    17988,1683377,3366754,5050131,6733508,10100262,20200524};
    int b[31];
    int main(){
        int t,x,y;
        scanf("%d",&t);
        while(t--){
            scanf("%d%d",&x,&y);
            for(int i=0;i<m;i++) b[i]=x/a[i];
            for(int i=m-1;i>=0;i--)
                for(int j=i+1;j<m;j++)
                    if(a[j]%a[i]==0) b[i]-=b[j];
            ll ans=0;
            for(int i=0;i<m;i++) ans+=1ll*b[i]*(y/(n/a[i]));
            printf("%lld
    ",ans);
        }
        return 0;
    }
    啊啊啊数学

    J.异或Tree

    看这个树上,还有在线更新的,除了树链剖分想不到其他的了,但是区间异或和不会处理,hhhhh,艹了,去看一下大佬怎么写的先了。

    区间异或和的和

    水完了,溜溜溜。

  • 相关阅读:
    catalina_home与catalina_base
    log4j配置
    lsof
    定时任务-crontab
    access日志配置
    java常识
    mysql事务隔离级别与实现原理
    文件描述符设置
    gpio 預設值
    synchronous interrupt and asynchronous interrupt
  • 原文地址:https://www.cnblogs.com/LMCC1108/p/12952213.html
Copyright © 2011-2022 走看看