• 2021暑期cf加训3


    比赛链接:https://codeforces.com/group/2g1PZcsgml/contest/339356

    A,C,F,K,17...前面题过得有点慢了,又仅做出四题。K题我一开始还提供了一个算法,后来WA了,发现有一种情况不能处理...orz;还是Y又想了一种做法,过了。

    A

    分析:

    用set维护每个窗户旋转后的四种哈希值。

    代码如下:

    #include<iostream>
    #include<set>
    #define ll long long
    using namespace std;
    int const N=115,bs=131,md=1e9+7;
    int R,C,r,c;
    char s[N][N];
    set<ll>st;
    bool fd(int sr,int sc)
    {
        ll hs=0;
        for(int i=sr;i<=sr+r-1;i++)
            for(int j=sc;j<=sc+c-1;j++)
                hs=(hs*bs%md+s[i][j])%md;
        return st.find(hs)!=st.end();
    }
    void add(int sr,int sc)
    {
        ll hs=0;
        for(int i=sr;i<=sr+r-1;i++)
            for(int j=sc;j<=sc+c-1;j++)
                hs=(hs*bs%md+s[i][j])%md;
        st.insert(hs);
        hs=0;
        for(int j=sc;j<=sc+c-1;j++)
            for(int i=sr+r-1;i>=sr;i--)
                hs=(hs*bs%md+s[i][j])%md;
        if(st.find(hs)==st.end())st.insert(hs);
        hs=0;
        for(int j=sc+c-1;j>=sc;j--)
            for(int i=sr;i<=sr+r-1;i++)
                hs=(hs*bs%md+s[i][j])%md;
        if(st.find(hs)==st.end())st.insert(hs);
        hs=0;
        for(int i=sr+r-1;i>=sr;i--)
            for(int j=sc+c-1;j>=sc;j--)
                hs=(hs*bs%md+s[i][j])%md;
        if(st.find(hs)==st.end())st.insert(hs);
    }
    int main()
    {
        scanf("%d%d",&R,&C); int ans=0;
        for(int i=1;i<=R;i++)scanf("%s",s[i]+1);
        for(int j=2;;j++)
            if(s[2][j]=='#'){c=j-2; break;}
        for(int i=2;;i++)
            if(s[i][2]=='#'){r=i-2; break;}
        for(int i=2;i<=R;i+=r+1)
            for(int j=2;j<=C;j+=c+1)
                if(!fd(i,j))ans++,add(i,j);//,printf("i=%d j=%d
    ",i,j);
        printf("%d
    ",ans);
        return 0;
    }
    me

    B

    分析:

    根据hall定理,若对于一个左部点集合A,它所有子集都满足子集直接相连的右部点的个数( geq )子集点数,那么存在一个完全匹配把A覆盖;

    所以可以( n*2^n )dfs得到两边所有满足条件的点集。dfs时从大到小搜索,利用子集的答案。注意数组大小T_T

    两边各取一个满足条件的集合,并起来的集合仍然满足条件;因为假设现在A(左部点)和B(右部点)各自满足条件,可以找到它们各自的一个完全匹配,把它们整体并起来,再进行匹配;建立一个有向图,对于所有A中的点a,连边a->b,其中b是它完全匹配中的点(可能是B中的也可能不是);对于所有B中的点b',连边b'->a',其中a'是它完全匹配中的点(可能是A中的也可能不是)。这样图中每个点的出度都为0或1,而且是A,B中的点出度为1,其他点出度为0。所以这个图只有链和简单环。在这些链和简单环上两个两个跳着选边(选第一、三、五……条),选中的边两个端点相互匹配;那么最后如果剩下没匹配的点,只可能是链的末尾点;但这个末尾点不是A,B中的点,也就是说A,B中的点都匹配上了,也就是说A和B的并集也满足条件。

    所以,答案就是这些满足条件两边的集合两两匹配后权值( geq t)的方案数。可以排序后双指针扫一遍。

    代码如下:

    #include<iostream>
    #include<vector>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    #define pb push_back
    using namespace std;
    int const N=25,M=(1<<22);//,M=(1<<20)+5;
    int n,m,t,eda[N],edb[N],a[2][N];
    bool can[M],vis[M];
    char s[N];
    vector<int>A,B;
    bool dfs(int s)
    {
        if(vis[s])return can[s];
        int b=0,sum=0; vis[s]=1; can[s]=1;
        for(int i=1;i<=n;i++)
            if(s&(1<<i))
            {
                can[s]&=dfs(s-(1<<i));
                b|=eda[i]; sum+=a[0][i];
            }
        int cnt1=0,cnt2=0;
        for(int i=1;i<=n;i++)if(s&(1<<i))cnt1++;
        for(int i=1;i<=m;i++)if(b&(1<<i))cnt2++;
        if(cnt1>cnt2)can[s]=0;
        if(can[s])A.pb(sum);
        //printf("cnt1=%d cnt2=%d can[%d]=%d
    ",cnt1,cnt2,s,can[s]);
        return can[s];
    }
    bool dfs2(int s)
    {
        if(vis[s])return can[s];
        int b=0,sum=0; vis[s]=1; can[s]=1;
        for(int i=1;i<=m;i++)
            if(s&(1<<i))
            {
                can[s]&=dfs2(s-(1<<i));
                b|=edb[i]; sum+=a[1][i];
            }
        int cnt1=0,cnt2=0;
        for(int i=1;i<=m;i++)if(s&(1<<i))cnt1++;
        for(int i=1;i<=n;i++)if(b&(1<<i))cnt2++;
        if(cnt1>cnt2)can[s]=0;
        if(can[s])B.pb(sum);
        return can[s];
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            scanf("%s",&s);
            for(int j=1;j<=m;j++)
                if(s[j-1]=='1')eda[i]|=(1<<j),edb[j]|=(1<<i);
        }
        for(int i=1;i<=n;i++)scanf("%d",&a[0][i]);
        for(int i=1;i<=m;i++)scanf("%d",&a[1][i]);
        scanf("%d",&t);
        dfs((1<<(n+1))-2);
        memset(vis,0,sizeof vis);
        dfs2((1<<(m+1))-2);//m+1!!
        //printf("as=%d bs=%d
    ",A.size(),B.size());
        sort(A.begin(),A.end());//
        sort(B.begin(),B.end());
        ll ans=0; int as=A.size(),bs=B.size(),pb=bs;
        for(int pa=0,pb=bs;pa<as;pa++)
        {
            while(pb>0&&A[pa]+B[pb-1]>=t)pb--;
            ans+=bs-pb;
        }
        printf("%lld
    ",ans);
        return 0;
    }
    /*
    3 3
    010
    111
    010
    1 2 3
    8 5 13
    21
    *//*
    3 2
    01
    11
    10
    1 2 3
    4 5
    8
    */
    me

    E

    分析:

    题目条件是( a^2+b^2+c^2=k*(a*b+b*c+c*a)+1 );

    如果固定(b,c),把上式改写成 (a^2-a*(k*b+k*c)+b^2+c^2-k*b*c-1=0 ),可以发现是个关于(a)的一元二次函数,对称轴是(a=k*b+k*c/2)。那么若((a,b,c))符合条件,((k*b+k*c-a,b,c))也符合条件,同理((a,k*a+k*c-b,c), (a,b,k*a+k*b-c))也符合条件。

    所以可以bfs;初始把((0,1,k)) 加入队列。过程中还要判断是否所有数彼此不同。因为数字可以有(100)位,很大,所以用python写;这样判断所有数不同也很方便。

    但是三种全加入会TLE;只写两种就过了。

    代码如下:

    lis=[0]
    k,n=map(int,input().split())
    q=[]
    hd=0
    cnt=0
    q.append((0,1,k))
    while(cnt<n):
        nw=q[hd]; hd+=1;
        a=nw[0]; b=nw[1]; c=nw[2];
        if not((a in lis) or (b in lis) or (c in lis) or (a==b) or (b==c) or (c==a)):
            cnt+=1
            print(a,b,c)
            lis.append(a); lis.append(b); lis.append(c);
        if k*b+k*c-a>0:
            q.append(tuple(sorted([k*b+k*c-a,b,c])))
        if k*a+k*c-b>0:
            q.append(tuple(sorted([a,k*a+k*c-b,c])))
        #if k*a+k*b-c>0:
        #    q.append(tuple(sorted([a,b,k*a+k*b-c])))
    me

    F

    分析:

    从最终状态来看,一步一步找下一个需要的,然后把它拆下来安到正确的地方;用链表(前驱+后继)维护。

    代码如下:

    #include<iostream>
    using namespace std;
    int const N=1e5+5;
    int n,pre[N],nxt[N],q[N],ans;
    bool vis[N];
    int rd()
    {
        int ret=0,f=1; char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
        while(c>='0'&&c<='9')ret=(ret<<3)+(ret<<1)+c-'0',c=getchar();
        return ret*f;
    }
    void work(int u)
    {
        vis[u]=1;
        if(nxt[u]!=q[u])
        {
            int v=u,af;
            while(nxt[v])af=nxt[v],nxt[v]=0,pre[af]=0,ans++,v=af;
            v=q[u];
            while(nxt[v])af=nxt[v],nxt[v]=0,pre[af]=0,ans++,v=af;
            v=q[u];
            if(pre[v])af=pre[v],pre[v]=0,nxt[af]=0,ans++;
            if(q[u])nxt[u]=q[u],pre[q[u]]=u,ans++;
        }
        if(q[u])work(q[u]);
    }
    int main()
    {
        n=rd();
        for(int i=1;i<=n;i++)
        {
            nxt[i]=rd();
            if(nxt[i])pre[nxt[i]]=i;
        }
        for(int i=1;i<=n;i++)q[i]=rd();
        for(int i=1;i<=n;i++)
            if(!vis[i])work(i);//,printf("i=%d ans=%d
    ",i,ans);
        printf("%d
    ",ans);
        return 0;
    }
    me

    H

    分析:

    首先,可以DP求出每个点作为中心时,最大的方块有多大;记为(p[i][j])。

    然后,从大到小枚举(p)遍历格子,连边,并查集维护连通块;当询问的起点和终点连通时,当前枚举到的(p)就是答案。

    看到一篇通过代码是上面这样写的;STL真方便。

    题解写的是连完边后在树上找LCA,而不维护并查集。想法也是一样的。

    看到很多人写了Kruskal重构树,目前不知是怎么做的。

    J

    分析:

    因为两对两对城市之间没有关系,所以我们一次专注于一对城市 (a,b) ;

    设从 a 到 b 为 0 ,从 b 到 a 为 1 ,那么得到一个 01 串;

    可以求出 a->b, a->b->a, b->a, b->a->b 的最小花费分别是多少,然后花费少的优先,贪心配对,再处理剩下单个的即可;

    可以用 pair, map, vector 等等来存各种状态,然后比较花费……因为这个写法太好了所以就模仿了,orz。

    代码如下:

    #include<iostream>
    #include<map>
    #include<vector>
    #define mkp make_pair
    #define pb push_back
    #define fst first
    #define scd second
    #define ll long long
    using namespace std;
    int const N=3e5+5,inf=2e9+5;
    int n,d,m,a[N];
    char t[5];
    ll ans;
    bool vis[N];
    map<pair<int,int>,int>o,r;
    map<pair<int,int>,vector<int> >mp;
    int main()
    {
        scanf("%d%d",&n,&d);
        for(int i=1;i<=d;i++)
        {
            scanf("%d",&a[i]);
            if(i==1)continue;
            pair<int,int> p=mkp(min(a[i-1],a[i]),max(a[i-1],a[i]));
            mp[p].pb(p.fst!=a[i-1]);
        }
        scanf("%d",&m); int u,v,s;
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%s%d",&u,&v,&t,&s);
            pair<int,int> p=mkp(u,v);
            if(t[0]=='O'){if(!o[p])o[p]=s; else o[p]=min(o[p],s);}
            else {if(!r[p])r[p]=s; else r[p]=min(r[p],s);}
        }
        for(map<pair<int,int>,vector<int> >::iterator it=mp.begin();it!=mp.end();it++)
        {
            int x=it->fst.fst,y=it->fst.scd;
            vector<int> v=it->scd,tmp; int sz=v.size();
            pair<int,int> p=mkp(x,y),p2=mkp(y,x);
            int r0=inf,r1=inf,o0=inf,o1=inf;
            if(o[p])o0=o[p]; if(o[p2])o1=o[p2];
            if(r[p])r0=r[p],o0=min(o0,r[p]); if(r[p2])r1=r[p2],o1=min(o1,r[p2]);
            //if(o[p]&&o[p2])r0=min(r0,o[p]+o[p2]),r1=min(r1,o[p2]+o[p]);
            if(o0<inf&&o1<inf)r0=min(r0,o0+o1),r1=min(r1,o0+o1);///
            for(int i=0;i<sz;i++)vis[i]=0;
            if(r0<r1)
            {
                for(int i=0;i<sz;i++)
                {
                    if(vis[i])continue;
                    if(!v[i])tmp.pb(i);
                    else if(tmp.size())vis[tmp.back()]=1,vis[i]=1,tmp.pop_back(),ans+=r0;
                }
                if(r1<inf)
                {
                    tmp.clear();
                    for(int i=0;i<sz;i++)
                    {
                        if(vis[i])continue;
                        if(v[i])tmp.pb(i);
                        else if(tmp.size())vis[tmp.back()]=1,vis[i]=1,tmp.pop_back(),ans+=r1;
                    }
                }
            }
            else
            {
                if(r1<inf)
                {
                    for(int i=0;i<sz;i++)
                    {
                        if(vis[i])continue;
                        if(v[i])tmp.pb(i);
                        else if(tmp.size())vis[tmp.back()]=1,vis[i]=1,tmp.pop_back(),ans+=r1;
                    }
                }
                if(r0<inf)
                {
                    tmp.clear();
                    for(int i=0;i<sz;i++)
                    {
                        if(vis[i])continue;
                        if(!v[i])tmp.pb(i);
                        else if(tmp.size())vis[tmp.back()]=1,vis[i]=1,tmp.pop_back(),ans+=r0;
                    }
                }
            }
            for(int i=0;i<sz;i++)
                if(!vis[i])ans+=(!v[i])?o0:o1;
        }
        printf("%lld
    ",ans);
        return 0;
    }
    /*
    2 5
    1 2 1 2 1
    4
    1 2 R 6
    1 2 O 3
    2 1 O 3
    1 2 R 5
    */
    /*
    4 10
    1 2 3 1 2 1 3 2 4 1
    9
    2 4 O 10
    1 3 R 1
    3 1 R 10
    2 3 R 20
    1 2 R 10
    1 2 O 20
    2 3 O 5
    3 2 O 5
    4 1 O 10
    */
    me

    K

    分析:

    一开始想四个四个做两遍,第一遍修改0011和1100的中间两个,第二遍修改0000和1111的中间两个,同时注意一下新出现的0011和1100。这样做完后字符串一定是010101或者1001001或者10001这几种情况。我误以为剩下的情况中没有贡献的位置的个数不会超过( n ),后来发现错了,10001这种就不符合。

    所以这题应该三个三个做,保证每三个中有( 2 )的贡献。可以分情况写,也可以直接一点;因为有些情况和前一位有关,所以别忘了当前的修改!

    代码如下:

    #include<iostream>
    #include<cstring>
    using namespace std;
    int const N=3e5+5;
    int n,cnt,ans[N];
    char s[N];
    char op(char c){return c=='0'?'1':'0';}
    int main()
    {
        scanf("%s",s+1); n=strlen(s+1);
        //if(s[1]=='0'&&s[2]=='0'&&s[3]=='0')ans[++cnt]=1;
        //if(s[1]=='1'&&s[2]=='1'&&s[3]=='1')ans[++cnt]=1;
        s[0]='0';
        for(int i=1;i<=n;i+=3)
        {
            if(s[i]=='0'&&s[i+1]=='0'&&s[i+2]=='0'){
                if(s[i-1]=='1')ans[++cnt]=i+1,s[i+2]=op(s[i+2]);///
                else ans[++cnt]=i;}
            else if(s[i]=='1'&&s[i+1]=='1'&&s[i+2]=='1'){
                if(s[i-1]=='1')ans[++cnt]=i;
                else ans[++cnt]=i+1,s[i+2]=op(s[i+2]);}///
            else if(s[i]==s[i+1]&&s[i]!=s[i+2])ans[++cnt]=i+1,s[i+2]=op(s[i+2]);///
            else if(s[i]!=s[i+1]&&s[i+1]==s[i+2])ans[++cnt]=i;
        }
        printf("%d
    ",cnt);
        for(int i=1;i<=cnt;i++)printf("%d%c",ans[i],i==cnt?'
    ':' ');
        return 0;
    }
    me1
    #include<iostream>
    #include<cstring>
    using namespace std;
    int const N=3e5+5;
    int n,cnt,ans[N];
    char s[N];
    int main()
    {
        scanf("%s",s+1); n=strlen(s+1);
        int lst=0;
        for(int i=1;i<=n;i+=3)
        {
            int a=s[i]-'0',b=s[i+1]-'0',c=s[i+2]-'0';
            int nw=(lst^a)+(a^b)+(b^c);
            if(nw>=2)continue;
            nw=(lst^a^1)+(a^b)+(b^1^c);
            if(nw>=2){ans[++cnt]=i; lst=c; continue;}
            nw=(lst^a)+(a^b^1)+(b^c);
            if(nw>=2){ans[++cnt]=i+1; lst=c^1; continue;}
        }
        printf("%d
    ",cnt);
        for(int i=1;i<=cnt;i++)printf("%d%c",ans[i],i==cnt?'
    ':' ');
        return 0;
    }
    me2

    L

    分析:

    构造题。

    关注某一个变量(下称“位置”)在三个序列中的取值,共有八种可能:000,001,010,011,100,101,110,111。状压储存。

    我们考虑如何限制才能让一个位置被限制在对应的状态。

    首先,如果状态是000或者111,我们可以直接限制:用( x -> !x )限制为0,( !x -> x )限制为1。

    其次,如果两个位置的状态是相同的或者正好相反的(如010和101),我们可以把它们绑定在一起,这样之后若确定一个位置上是0或1,另一个位置也就随之确定了。用( x1 -> x2, x2 -> x1)绑定相同的位置,相反的位置把(x2)变成(!x2),就和相同的一样了。

    绑定完成后,剩余不确定的就只可能有三种:001或110,010或101,100或011。

    如果没有剩余这些情况,说明所有位置都固定是0或1,那么直接输出答案。

    如果只剩余了一种,也就是全局的不确定性只和一个位置取0还是取1有关,那么合法的情况只有两种而不会有三种,输出-1。

    如果剩余了两种,它们01组合可以有四种情况,而题目给出的必然是其中三种。所以需要再加一个条件来使第四种不合法。比如想让00不合法,就加一个( !x1 -> x2 )。

    如果剩余了三种,它们两两之间彼此有三种合法情况;这些合法情况相互组合,无论如何总合法情况都会大于三种。这里我也没有(不太会)严谨的证明,只是在纸上画了画(如果把一个位置分成0和1两个点,两位置间合法情况连边,那么两个位置之间会有三条边;总合法情况就是从一个点出发,绕了一圈又回到了这个点。画了一番,总会有大于三种总合法情况;似乎有某些奇妙的原因……我还没有参透)。所以这种也直接输出-1。

    代码如下:

    #include<iostream>
    #include<cstring>
    using namespace std;
    int n,a[55],ans,anum,num,pos[10];
    bool vis[10],vistp[10];
    struct Nd{
        int id,tp,to;//tp: 0:is0 / 7:is1 / 1:same / 2:op / 3:not00 / 4:not01 / 5:not10 / 6:not11
    }pr[500];
    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 main()
    {
        scanf("%d",&n);
        for(int i=0;i<=2;i++)
            for(int j=1;j<=n;j++)
                if(rd())a[j]|=(1<<i);
        int p1=-1,p2=-1;
        for(int j=1;j<=n;j++)
        {
            if(a[j]==0)pr[++ans]=(Nd){j,0,-1},anum++;
            else if(a[j]==7)pr[++ans]=(Nd){j,7,-1},anum++;
            else
            {
                int x=a[j];
                if(vis[x]||vis[7-x])
                {
                    if(vis[x])
                        pr[++ans]=(Nd){j,1,pos[x]},anum+=2;
                    else
                        pr[++ans]=(Nd){j,2,pos[7-x]},anum+=2;
                }
                else 
                {
                    num++; vis[x]=1; pos[x]=j;
                    if(p1==-1)p1=j; else p2=j;
                }
            }
        }
        if(num==1||num==3){printf("-1
    "); return 0;}
        if(num==2)
        {
            int x=a[p1],y=a[p2];
    //vistp:0--00, 1--01, 2--10, 3--11
            for(int i=0;i<=2;i++)
            {
                int t=(1<<i);
                if((x&t)&&(y&t))vistp[3]=1;
                else if(x&t)vistp[2]=1;
                else if(y&t)vistp[1]=1;
                else vistp[0]=1;
            }
            for(int i=0;i<=3;i++)
                if(!vistp[i])pr[++ans]=(Nd){p1,i+3,p2},anum+=1;//
        }
        printf("%d
    ",anum);
        for(int i=1;i<=ans;i++)
        {
            p1=pr[i].id,p2=pr[i].to; int tp=pr[i].tp;
            if(tp==0)printf("x%d -> !x%d
    ",p1,p1);
            if(tp==7)printf("!x%d -> x%d
    ",p1,p1);
            if(tp==1)printf("x%d -> x%d
    x%d -> x%d
    ",p1,p2,p2,p1);
            if(tp==2)printf("x%d -> !x%d
    !x%d -> x%d
    ",p1,p2,p2,p1);
            // if(tp==3)printf("x%d -> x%d
    x%d -> !x%d
    !x%d -> !x%d
    ",p1,p2,p1,p2,p1,p2);
            // if(tp==4)printf("!x%d -> x%d
    x%d -> !x%d
    x%d -> x%d
    ",p1,p2,p1,p2,p1,p2);
            // if(tp==5)printf("!x%d -> !x%d
    x%d -> !x%d
    !x%d -> x%d
    ",p1,p2,p1,p2,p1,p2);
            // if(tp==6)printf("x%d -> x%d
    !x%d -> x%d
    !x%d -> !x%d
    ",p1,p2,p1,p2,p1,p2);
            if(tp==3)printf("!x%d -> x%d
    ",p1,p2);
            if(tp==4)printf("!x%d -> !x%d
    ",p1,p2);
            if(tp==5)printf("x%d -> x%d
    ",p1,p2);
            if(tp==6)printf("x%d -> !x%d
    ",p1,p2);
        }
        return 0;
    }
    me
  • 相关阅读:
    iOS APP程序启动原理
    关于组合式继承和寄生式继承的个人理解
    servlet session 相关
    hadoop配置远程客户端
    将普通工程转为mvn标准工程(main resources)
    log4j2 配置文件
    mvn生成runnablejar 的方法
    普通工程转为mvn工程
    java ReentrantLock可重入锁功能
    在mapreduce中做分布式缓存的问题
  • 原文地址:https://www.cnblogs.com/Zinn/p/15107444.html
Copyright © 2020-2023  润新知