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\n",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("\n");
        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\n");
    }
    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\n",sz);
    for(int i=0;i<sz;i++){
        for(int j=0;j<sz;j++){
            printf("%d",a[i][j]);
        }
        printf("\n");
    }
    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\n",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\n",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\n",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\n",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\n",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\n",ans);
    }
    return 0;
}
View Code

posted on 2018-07-26 12:45  我过了样例耶  阅读(204)  评论(0编辑  收藏  举报

导航