比赛链接: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; }
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 */
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])))
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; }
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 */
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; }
#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; }
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; }