第十五届中北大学算法与程序设计竞赛

比赛地址:传过去送的门

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

之前的随笔里的代码因为设置了行内标号,现在复制代码前面都带上了行号,而且没法点击复制,哪位能人异士,信男信女懂怎么觉得,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,艹了,去看一下大佬怎么写的先了。

区间异或和的和

水完了,溜溜溜。

原文地址:https://www.cnblogs.com/LMCC1108/p/12952213.html