• cf 1559(div2)


    比赛链接:https://codeforces.com/contest/1559

    因为一些不良代码习惯,前期做得慢了;掉分T_T

    D2

    题意:

    给两个森林,要求加相同的边,求在两边都不生成环的情况下最多能加多少条边,并输出能加的边。

    分析:

    可以用set,较暴力地做。

    首先,左边森林和右边森林都各自有一些连通块。当我们想要在两点之间连边,需要保证它们在左右两边都不在同一个连通块里。

    最终状态一定是某一边的森林变成了一棵树(原因下面细讲)——由于加边是同时的,所以变成树的森林是一开始连通块就比较少的那个森林。下面我们把它作为左边森林。

    如果我们在连接左边的两个连通块时,能知道这两个连通块里包含的右边连通块的情况,并且选出两个在右边不是同一个连通块的两个点,连边,就好了。

    为了方便想,把左边的连通块成为“行”,右边的连通块成为“列”,行列相交的格子里存放的是同时在两个连通块里的点。

    我们用一个map来存格子的“代表点”:mp[a][b]=p表示用p这个点来代表同时在左边a连通块、右边b连通块的那些点。

    然后,我们给每一行开一个set,里面存放它的点涉及到的列;给每一列开一个set,里面存放它的点涉及到的行。

    这样,当我们想要合并两行时,可以从它们的set中取出两个不同的列,通过map找到对应的两个代表点,然后把它们连边。

    问题是,这两行的set中一定有两个不同的元素吗?万一所有的元素都是相同的怎么办?

    这需要我们再安排一个合并的顺序——将行按set的大小排序,每次取最大的两个合并。如果最大的那个行的set也只有一个元素,那么所有行的set都只有一个元素;但是前面我们选择让连通块较少的森林作为左边森林,也就是行数(<)列数;所以这种情况一定是行数(=)列数,那么每一行的set里存的元素就是彼此不同的。如果最大的那个行有两个及以上元素,那必然可以找到一个和次大的行中第一个元素不同的元素。

    所以我们每次一定能在两行的set中找到符合条件的两个元素,对应了左边森林的两个连通块和右边森林的两个连通块。通过map可以找到同时在左右对应连通块中的代表点,把两个代表点连边即可。

    这个过程也告诉我们,最终状态一定是左边森林只剩下了一个连通块,也就是变成了一棵树。

    接着,连边了以后我们需要合并两行和两列——这里当然是把小的往大的合并。需要改变的信息是set和map:把被合并者set中的每个元素都放进合并者的set里,并且在那个元素的set里把被合并者删除,加入合并者;对应被合并者的map也要改成对应合并者。时间复杂度(O(nlog^2(n)))。

    当然,我们区分各行和各列是用并查集的。

    代码如下:

    #include<iostream>
    #include<map>
    #include<set>
    #include<algorithm>
    #define mkp make_pair
    using namespace std;
    int const N=1e5+5;
    int n,m1,m2,fa[3][N],ans,pl[N],pr[N],t1,t2;
    set<int>row[N],col[N];
    set<pair<int,int> >rows;
    map<int,int>mp[N];
    int rd()
    {
        int ret=0,f=1; char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
        while(ch>='0'&&ch<='9')ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar();
        return ret*f;
    }
    int find(int t,int x)
    {
        if(fa[t][x]!=x)fa[t][x]=find(t,fa[t][x]);
        return fa[t][x];
    }
    void merge1(int x,int y)
    {
        for(int it:row[y])
        {
            row[x].insert(it);
            col[it].erase(y); col[it].insert(x);
            mp[x][it]=mp[y][it];
        }
    }
    void merge2(int x,int y)
    {
        for(int it:col[y])
        {
            col[x].insert(it);
            row[it].erase(y); row[it].insert(x);
            mp[it][x]=mp[it][y];
        }
    }
    int main()
    {
        n=rd(); m1=rd(); m2=rd(); t1=1; t2=2;
        for(int t=1;t<=2;t++)
            for(int i=1;i<=n;i++)fa[t][i]=i;
        for(int i=1,x,y;i<=m1;i++)
        {
            x=rd(); y=rd();
            x=find(1,x); y=find(1,y); fa[1][x]=y;
        }
        for(int i=1,x,y;i<=m2;i++)
        {
            x=rd(); y=rd();
            x=find(2,x); y=find(2,y); fa[2][x]=y;
        }
        if(m1<m2)swap(t1,t2);
        for(int i=1,p1,p2;i<=n;i++)
        {
            p1=find(t1,i); p2=find(t2,i);
            row[p1].insert(p2); col[p2].insert(p1);
            mp[p1][p2]=i;
        }
        for(int i=1;i<=n;i++)
            if(find(t1,i)==i)rows.insert(mkp(-row[i].size(),i));
        while(rows.size()>1)
        {
            int x=(*rows.begin()).second;
            rows.erase(rows.begin());
            int y=(*rows.begin()).second;
            rows.erase(rows.begin());
            int a=*row[x].begin();
            int b=*row[y].begin();
            if(a==b)
            {
                set<int>::iterator it=row[x].begin();
                it++; a=*it;
            }
            ans++; pl[ans]=mp[x][a]; pr[ans]=mp[y][b];
            if(col[a].size()<col[b].size())swap(a,b);
            merge1(x,y); merge2(a,b);
            rows.insert(mkp(-row[x].size(),x));
        }
        printf("%d
    ",ans);
        for(int i=1;i<=ans;i++)printf("%d %d
    ",pl[i],pr[i]);
        return 0;
    }
    View Code

    E

    分析:

    (gcd=1)不好处理,但(gcd)等于某个具体的数(的倍数)的方案数好求。所以我们考虑容斥做。

    对于(1)到(m)中的每个数(x),都求一下(gcd=x)的方案数——这个可以用背包做,只要枚举(x)的倍数即可。但是比赛时卡在时间复杂度的计算上了,以为是(O(nm^2)),还是太不熟悉……这个时间复杂度其实是(O(nmlnm))的,所以可做。

    求出这个以后从大到小容斥,把原来的“(gcd)是(x)的倍数的方案数”容斥成“(gcd)恰好是(x)的方案数”,就得到我们要的(gcd=1)的方案数了。

    背包DP在转移的时候要用一下差分,否则会TLE……也不能开滚动数组,否则会TLE……看来是每次异或(1)让时间太长了……

    代码如下:

    #include<iostream>
    #include<cstring>
    #define ll long long
    using namespace std;
    int const md=998244353,N=55,M=1e5+5;
    int n,m,l[N],r[N],f[N][M],ans[M];
    int add(int x,int y){ll ret=x+y; return (ret>=md)?ret-md:ret;}
    /*
    int cal(int d)
    {
        memset(f,0,sizeof f); 
        int t=0,M=m/d; f[1][0]=1;
        for(int i=1;i<=n;i++)
        {
            int L=(l[i]+(d-1))/d,R=r[i]/d;
            for(int j=0;j<=M;j++)f[t][j]=0;
            for(int j=0;j+L<=M;j++)
            {
                f[t][j+L]=add(f[t][j+L],f[t^1][j]);
                f[t][min(M+1,j+R+1)]=add(f[t][min(M+1,j+R+1)],md-f[t^1][j]);
            }
                // for(int k=L;k<=R;k++)
                //     if(j-k>=0)f[t][j]=((ll)f[t][j]+f[t^1][j-k])%md;
            for(int j=1;j<=M;j++)
                f[t][j]=add(f[t][j],f[t][j-1]);
            t^=1;
        }
        int ret=0;
        for(int i=1;i<=M;i++)
            ret=add(ret,f[t^1][i]);
        return ret;
    }
    */
    int cal(int d)
    {
        int M=m/d;
        for(int i=0;i<=n;i++)
            for(int j=0;j<=M;j++)f[i][j]=0;
        f[0][0]=1;
        for(int i=1;i<=n;i++)
        {
            int L=(l[i]+(d-1))/d,R=r[i]/d;
            for(int j=0;j+L<=M;j++)
            {
                f[i][j+L]=add(f[i][j+L],f[i-1][j]);
                f[i][min(M+1,j+R+1)]=add(f[i][min(M+1,j+R+1)],md-f[i-1][j]);
            }
            for(int j=1;j<=M;j++)
                f[i][j]=add(f[i][j],f[i][j-1]);
        }
        int ret=0;
        for(int i=1;i<=M;i++)
            ret=add(ret,f[n][i]);
        return ret;
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)scanf("%d%d",&l[i],&r[i]);
        for(int i=1;i<=m;i++)ans[i]=cal(i);
        for(int i=m;i>=1;i--)
            for(int j=2*i;j<=m;j+=i)
                ans[i]=add(ans[i],md-ans[j]);
        printf("%d
    ",ans[1]);
        return 0;
    }
    View Code
  • 相关阅读:
    递归函数
    js原生代码添加表格(行,列用户选择)
    Vue列表数组检测及列表过滤
    字符,图片及视频存储
    小程序js-api简介及操作
    小程序开发-了解
    外购入库单审核可以,删除失败,提示采购单据严格按照数量控制,收料通知单关联数量不能大或负数
    PDO基础应用之异常处理
    进程池用法
    [转]解决Error: That port is already in use.
  • 原文地址:https://www.cnblogs.com/Zinn/p/15146644.html
Copyright © 2020-2023  润新知