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\n",i,j);
    printf("%d\n",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\n",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\n",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\n",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\n",i,ans);
    printf("%d\n",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\n",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\n",cnt);
    for(int i=1;i<=cnt;i++)printf("%d%c",ans[i],i==cnt?'\n':' ');
    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\n",cnt);
    for(int i=1;i<=cnt;i++)printf("%d%c",ans[i],i==cnt?'\n':' ');
    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\n"); 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\n",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\n",p1,p1);
        if(tp==7)printf("!x%d -> x%d\n",p1,p1);
        if(tp==1)printf("x%d -> x%d\nx%d -> x%d\n",p1,p2,p2,p1);
        if(tp==2)printf("x%d -> !x%d\n!x%d -> x%d\n",p1,p2,p2,p1);
        // if(tp==3)printf("x%d -> x%d\nx%d -> !x%d\n!x%d -> !x%d\n",p1,p2,p1,p2,p1,p2);
        // if(tp==4)printf("!x%d -> x%d\nx%d -> !x%d\nx%d -> x%d\n",p1,p2,p1,p2,p1,p2);
        // if(tp==5)printf("!x%d -> !x%d\nx%d -> !x%d\n!x%d -> x%d\n",p1,p2,p1,p2,p1,p2);
        // if(tp==6)printf("x%d -> x%d\n!x%d -> x%d\n!x%d -> !x%d\n",p1,p2,p1,p2,p1,p2);
        if(tp==3)printf("!x%d -> x%d\n",p1,p2);
        if(tp==4)printf("!x%d -> !x%d\n",p1,p2);
        if(tp==5)printf("x%d -> x%d\n",p1,p2);
        if(tp==6)printf("x%d -> !x%d\n",p1,p2);
    }
    return 0;
}
me

 

posted @ 2021-08-06 21:48  Zinn  阅读(28)  评论(0编辑  收藏  举报