【2018年全国多校算法寒假训练营练习比赛(第四场)】
上一场自己状态爆表;这一场队友爆表,自己捡表了;题目呢,总的来说不难,相比于前面几场比赛来说,关键在于如何建图。
像D题,在我的上一个博客里面就是一道同理的题,只需要求入度为0和出度为0的点最大数,然后特判已经强连通(比如只有一个点)的情况。
可以看看我上一个博客,可能会对此比赛有帮助。
【A 石油采集】
题解:二分匹配之匈牙利。
【B 道路建设】
题解:并查集。
【C 求交集】
题解:双指针扫一遍即可。
【D 小明的挖矿之旅】
题解:求度为0的点数量。
【E 通知小弟】
题解:处理入度为0的新点;
【F Call to your teacher】
题解:水
【G 老子的意大利炮呢】
题解:分层+优先队列SPFA。
【H 老子的全排列呢】
题解:STL之permutation或者试一试康拓展开。
-------------------------------代码-----------------------------
【A】
12月份刚好看过队友做过此题,当然那个题我还懵b了,所以记得。
可以先去看看POJ 2446 ,POJ3020。附上队友博客:LZH
#include<iostream> #include<cstdio> #include<cstring> #include<ctime> #include<cstdlib> #include<algorithm> #include<cmath> #include<string> using namespace std; int read(){ int xx=0,ff=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')ff=-1;ch=getchar();} while(ch>='0'&&ch<='9'){xx=(xx<<3)+(xx<<1)+ch-'0';ch=getchar();} return xx*ff; } const int ws_[4]={0,1,0,-1},ad_[4]={1,0,-1,0}; int N,cas=0; char mp[55][55]; inline int id(int x,int y) {return (x-1)*N+y;} int lin[3100],len; struct edge{ int y,next; }e[100010]; inline void insert(int xx,int yy){ e[++len].next=lin[xx]; lin[xx]=len; e[len].y=yy; } inline void ins(int xx,int yy) {insert(xx,yy),insert(yy,xx);} bool vis[3100]; int match[3100]; bool hun(int x){ for(int i=lin[x];i;i=e[i].next) if(!vis[e[i].y]){ vis[e[i].y]=1; if(match[e[i].y]==0||hun(match[e[i].y])){ match[e[i].y]=x; match[x]=e[i].y; return 1; } } return 0; } int main(){ //freopen("in","r",stdin); for(int T=read();T;T--){ printf("Case %d: ",++cas); char ch; N=read(); for(int i=1;i<=N;i++) for(int j=1;j<=N;j++){ ch=getchar(); while(ch==' '||ch=='\n') ch=getchar(); mp[i][j]=ch; } memset(lin,0,sizeof(lin));len=0; for(int i=1;i<=N;i++) for(int j=1;j<=N;j++) if(mp[i][j]=='#') for(int k=0;k<=3;k++){ int tx=i+ws_[k],ty=j+ad_[k]; if(tx<=0||ty<=0||tx>N||ty>N) continue; if(mp[tx][ty]=='#') ins(id(i,j),id(tx,ty)); } memset(match,0,sizeof(match)); int ans=0; for(int i=1;i<=N;i++) for(int j=1;j<=N;j++) if(mp[i][j]=='#'){ if(!match[id(i,j)]){ memset(vis,0,sizeof(vis)); if(hun(id(i,j))) ans++; } } printf("%d\n",ans); } }
【B】
并查集
#include<iostream> #include<cstdio> #include<cstring> #include<ctime> #include<cstdlib> #include<algorithm> #include<cmath> #include<string> using namespace std; int read(){ int xx=0,ff=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')ff=-1;ch=getchar();} while(ch>='0'&&ch<='9'){xx=(xx<<3)+(xx<<1)+ch-'0';ch=getchar();} return xx*ff; } int C,N,M; struct edge{ int x,y,v; bool friend operator<(const edge&A,const edge&B) {return A.v<B.v;} }e[10010]; int father[110]; int getfather(int x){ if(father[x]==x) return x; return father[x]=getfather(father[x]); } int main(){ //freopen("in","r",stdin); while(scanf("%d",&C)!=EOF){ N=read(),M=read(); for(int i=1;i<=N;i++) e[i].x=read(),e[i].y=read(),e[i].v=read(); sort(e+1,e+1+N); for(int i=1;i<=M;i++) father[i]=i; int cnt=0; long long ans=0; for(int i=1;i<=N;i++){ int f1=getfather(e[i].x),f2=getfather(e[i].y); if(f1==f2) continue; cnt++; ans+=e[i].v; father[f2]=f1; if(cnt==M-1) break; } if(ans<=C) printf("Yes\n"); else printf("No\n"); } return 0; }
【C】
由于已经有序,而且集合具有互异性,可以用双指针。
假设两个队伍,每次比较队首,小的一方后移队首。
#include<iostream> #include<cstdio> #include<cstring> #include<ctime> #include<cstdlib> #include<algorithm> #include<cmath> #include<string> using namespace std; int read(){ int xx=0,ff=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')ff=-1;ch=getchar();} while(ch>='0'&&ch<='9'){xx=(xx<<3)+(xx<<1)+ch-'0';ch=getchar();} return xx*ff; } const int maxn=1000010; int a[maxn],b[maxn],N,M; int ans[maxn],tp; int main(){ while(scanf("%d %d",&N,&M)!=EOF){ for(int i=1;i<=N;i++) a[i]=read(); for(int i=1;i<=M;i++) b[i]=read(); int i=1,j=1,tp=0; while(i<=N&&j<=M){ if(a[i]==b[j]) ans[++tp]=a[i],i++,j++; else if(a[i]>b[j]) j++; else i++; } if(tp==0) printf("empty\n"); else { for(int k=1;k<tp;k++) printf("%d ",ans[k]); printf("%d\n",ans[tp]); } } return 0; }
【D】
就是问有向图至少加几条有向边使得图成为强连通,结论是:当已经强连通时,答案为0(此情况就是所谓的特判);否则,答案=max(出度为0数,入度为0数)。
对于上面的结论,我以前证明过。但是找了下没找到对应的博客。
Tarjan缩点建图,求度为0的点数。我的代码如下,但可能数据有点大,只通过了87%。
//有向图缩点 ,注意scc_cnt=1时。 #include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=1000010; int Laxt[maxn],Next[maxn<<1],To[maxn<<1],cnt,n,m; int dfn[maxn],low[maxn],times,scc_cnt,scc[maxn]; int instc[maxn],stc[maxn],top,ans1,ans2; int ind[maxn],oud[maxn]; char chr[1010][1010]; void update() { cnt=times=scc_cnt=top=ans1=ans2=0; memset(Laxt,0,sizeof(Laxt)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(scc,0,sizeof(scc)); memset(instc,0,sizeof(instc)); memset(stc,0,sizeof(stc)); memset(ind,0,sizeof(ind)); memset(oud,0,sizeof(oud)); } void add(int u,int v) { Next[++cnt]=Laxt[u]; Laxt[u]=cnt; To[cnt]=v; } void dfs(int u) { dfn[u]=low[u]=++times; stc[++top]=u; instc[u]=1; for(int i=Laxt[u];i;i=Next[i]){ int v=To[i]; if(!dfn[v]){ dfs(v); low[u]=min(low[u],low[v]); } else if(instc[v]){ low[u]=min(low[u],dfn[v]); } } if(dfn[u]==low[u]){ scc_cnt++; while(true){ int x=stc[top--]; scc[x]=scc_cnt; instc[x]=0; if(x==u) break; } } } void tarjan() { for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ if(chr[i][j]=='.'&&!dfn[(i-1)*m+j]) dfs((i-1)*m+j); } for(int i=1;i<=n*m;i++) for(int j=Laxt[i];j;j=Next[j]){ if(scc[i]!=scc[To[j]]) { ind[scc[To[j]]]++; oud[scc[i]]++; } } for(int i=1;i<=scc_cnt;i++){ if(ind[i]==0) ans1++; if(oud[i]==0) ans2++; } } int main() { while(~scanf("%d%d",&n,&m)){ update(); for(int i=1;i<=n;i++) scanf("%s",chr[i]+1); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ if(chr[i][j]=='.'){ if(j+1<=m&&chr[i][j+1]=='.') add((i-1)*m+j,(i-1)*m+j+1); if(i+1<=n&&chr[i+1][j]=='.') add((i-1)*m+j,i*m+j); } } tarjan(); if(scc_cnt==1) printf("0\n"); else printf("%d\n",max(ans1,ans2)); } return 0; }
利用方向只能像东南这个条件,直接检查是是不是入度为0的点,或者出度为0的点。
#include<iostream> #include<cstdio> #include<cstring> #include<ctime> #include<cstdlib> #include<algorithm> #include<cmath> #include<string> using namespace std; int read(){ int xx=0,ff=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')ff=-1;ch=getchar();} while(ch>='0'&&ch<='9'){xx=(xx<<3)+(xx<<1)+ch-'0';ch=getchar();} return xx*ff; } const int ws_[4]={0,1,0,-1},ad_[4]={1,0,-1,0}; int N,M; char mp[1010][1010]; bool vis[1010][1010]; int tp1,tp2,cnt; bool check(int xx,int yy){ if(xx<=0||yy<=0||xx>N||yy>M) return 0; return 1; } void dfs(int xx,int yy){ vis[xx][yy]=1; if((!check(xx+ws_[2],yy+ad_[2]))||mp[xx+ws_[2]][yy+ad_[2]]=='#') if((!check(xx+ws_[3],yy+ad_[3]))||mp[xx+ws_[3]][yy+ad_[3]]=='#') tp1++; if((!check(xx+ws_[0],yy+ad_[0]))||mp[xx+ws_[0]][yy+ad_[0]]=='#') if((!check(xx+ws_[1],yy+ad_[1]))||mp[xx+ws_[1]][yy+ad_[1]]=='#') tp2++; int tx,ty; for(int k=0;k<=3;k++){ tx=xx+ws_[k],ty=yy+ad_[k]; if(!check(tx,ty)) continue; if((!vis[tx][ty])&&mp[tx][ty]=='.') dfs(tx,ty); } } int main(){ //freopen("in","r",stdin); while(scanf("%d %d",&N,&M)!=EOF){ cnt=0; for(int i=1;i<=N;i++) for(int j=1;j<=M;j++){ char ch=getchar(); while(ch==' '||ch=='\n') ch=getchar(); mp[i][j]=ch; if(mp[i][j]=='.') cnt++; } int ans,ans1=0,ans2=0; memset(vis,0,sizeof(vis)); for(int i=1;i<=N;i++) for(int j=1;j<=M;j++) if(mp[i][j]=='.'&&(!vis[i][j])){ tp1=tp2=0; dfs(i,j); ans1+=tp1,ans2+=tp2; } ans=max(ans1,ans2); if(cnt==1) ans=0; printf("%d\n",ans); } return 0; }
【E】
显然,缩点,重新建图,只有入度为0的点(缩点之后的)是需要考虑的,如果入度为0的点(缩点之后,其实是个集合)里面如果没有可以通知到的,那么“-1”;
如果入度为0的点大于m,那么“-1”;
现在看来,只需求入度为0的有哪些,然后验证每个点(集合)是否存在可以通知的人即可,而不需重新构图。
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=1010; int Laxt[maxn],Next[maxn*250],To[maxn*250],cnt,n,m; int dfn[maxn],low[maxn],times,scc_cnt,scc[maxn]; int instc[maxn],stc[maxn],top; int ind[maxn],ans,p[maxn],ok[maxn]; void update() { cnt=times=scc_cnt=top=ans=0; memset(Laxt,0,sizeof(Laxt)); memset(dfn,0,sizeof(dfn)); memset(scc,0,sizeof(scc)); memset(instc,0,sizeof(instc)); memset(stc,0,sizeof(stc)); memset(ind,0,sizeof(ind)); memset(p,0,sizeof(p)); memset(ok,0,sizeof(ok)); } void add(int u,int v) { Next[++cnt]=Laxt[u]; Laxt[u]=cnt; To[cnt]=v; } void dfs(int u) { dfn[u]=low[u]=++times; stc[++top]=u; instc[u]=1; for(int i=Laxt[u];i;i=Next[i]){ int v=To[i]; if(!dfn[v]){ dfs(v); low[u]=min(low[u],low[v]); } else if(instc[v]){ low[u]=min(low[u],dfn[v]); } } if(dfn[u]==low[u]){ scc_cnt++; while(true){ int x=stc[top--]; scc[x]=scc_cnt; instc[x]=0; if(x==u) break; } } } void tarjan() { for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i); for(int i=1;i<=n;i++) for(int j=Laxt[i];j;j=Next[j]){ if(scc[i]!=scc[To[j]]) { ind[scc[To[j]]]++; } } for(int i=1;i<=scc_cnt;i++){ if(ind[i]==0) ans++; } if(ans>m){ printf("-1\n"); return ; } for(int i=1;i<=n;i++){ if(ind[scc[i]]==0&&p[i]==1) ok[scc[i]]=1; } for(int i=1;i<=scc_cnt;i++) if(ind[i]==0&&!ok[i]) { printf("-1\n"); return ; } printf("%d\n",ans); return ; } int main() { while(~scanf("%d%d",&n,&m)){ update(); int x,y; for(int i=1;i<=m;i++) scanf("%d",&x),p[x]=1; for(int i=1;i<=n;i++) { scanf("%d",&y); for(int j=1;j<=y;j++){ scanf("%d",&x); add(i,x); } } tarjan(); } return 0; }
【F】
水题。
#include<iostream> #include<cstdio> #include<cstring> #include<ctime> #include<cstdlib> #include<algorithm> #include<cmath> #include<string> using namespace std; int read(){ int xx=0,ff=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')ff=-1;ch=getchar();} while(ch>='0'&&ch<='9'){xx=(xx<<3)+(xx<<1)+ch-'0';ch=getchar();} return xx*ff; } const int maxn=60,maxm=2010; int N,M; int lin[maxn],len; struct edge{ int y,next; }e[maxm<<1]; inline void insert(int xx,int yy){ e[++len].next=lin[xx]; lin[xx]=len; e[len].y=yy; } bool vis[maxn]; void dfs(int x){ vis[x]=1; for(int i=lin[x];i;i=e[i].next) if(!vis[e[i].y]) dfs(e[i].y); } int main(){ //freopen("in","r",stdin); while(scanf("%d %d",&N,&M)!=EOF){ int t1,t2; memset(lin,0,sizeof(lin));len=0; for(int i=1;i<=M;i++){ t1=read(),t2=read(); insert(t1,t2); } memset(vis,0,sizeof(vis)); dfs(1); if(vis[N]) cout<<"Yes\n"; else cout<<"No\n"; } return 0; }
【G】
大家应该会感觉是最短路算法,或者搜索?因为记忆化后节点数也不多。
0代表什么都不拿....7代表1+1+1,拿了三样,这样就是分层处理。
然后每次4个方向。。。我tm居然列举出来,而不是for语句合并。。。。导致代码太丑了。。。
这里用的分层+SPFA,然后用BFS的方法(优先单调队列+STL结构体 实现)搜索的。
由于后面复制的时候排版乱了,将就吧。。。
#include<cstdio> #include<cstdlib> #include<iostream> #include<cstring> #include<queue> #include<algorithm> using namespace std; char chr[110][110]; int dis[8][110][110],times[10],n,m; int sx,sy,xx[4],yy[4],ex,ey,t1,t2,t3; const int inf=0x7fffffff; struct In { int opt1; int x1; int y1; int stp1; In(int i, int j,int k,int t):opt1(i), x1(j), y1(k), stp1(t){} }; bool operator < (const In& x, const In& y) { return x.stp1 > y.stp1; } priority_queue<In>q; void dfs() { while(!q.empty()) { In tmp=q.top();q.pop(); int opt=tmp.opt1,x=tmp.x1,y=tmp.y1,stp=tmp.stp1; if(opt==7&&x==ex&&y==ey) return ; if(opt<7){//可以FQ if(x+1<=n) { In temp(opt,x+1,y,stp+times[opt]); if(dis[opt][x+1][y]>stp+times[opt]) dis[opt][x+1][y]=stp+times[opt],q.push(temp); for(int i=1;i<=3;i++){ if(x==xx[i]&&y==yy[i]&&!((opt>>(i-1))&1)) { In temp(opt+(1<<(i-1)),x+1,y,stp+times[opt+(1<<(i-1))]); if(dis[opt+(1<<(i-1))][x+1][y]>stp+times[opt+(1<<(i-1))]) dis[opt+(1<<(i-1))][x+1][y]=stp+times[opt+(1<<(i-1))],q.push(temp); } } } if(x-1>=1){ In temp(opt,x-1,y,stp+times[opt]); if(dis[opt][x-1][y]>stp+times[opt]) dis[opt][x-1][y]=stp+times[opt],q.push(temp); for(int i=1;i<=3;i++){ if(x==xx[i]&&y==yy[i]&&!((opt>>(i-1))&1)) { In temp(opt+(1<<(i-1)),x-1,y,stp+times[opt+(1<<(i-1))]); if(dis[opt+(1<<(i-1))][x-1][y]>stp+times[opt+(1<<(i-1))]) dis[opt+(1<<(i-1))][x-1][y]=stp+times[opt+(1<<(i-1))],q.push(temp); } } } if(y-1>=1){ In temp(opt,x,y-1,stp+times[opt]); if(dis[opt][x][y-1]>stp+times[opt]) dis[opt][x][y-1]=stp+times[opt],q.push(temp); for(int i=1;i<=3;i++){ if(x==xx[i]&&y==yy[i]&&!((opt>>(i-1))&1)) { In temp(opt+(1<<(i-1)),x,y-1,stp+times[opt+(1<<(i-1))]); if(dis[opt+(1<<(i-1))][x][y-1]>stp+times[opt+(1<<(i-1))]) dis[opt+(1<<(i-1))][x][y-1]=stp+times[opt+(1<<(i-1))],q.push(temp); } } } if(y+1<=m){ In temp(opt,x,y+1,stp+times[opt]); if(dis[opt][x][y+1]>stp+times[opt]) dis[opt][x][y+1]=stp+times[opt],q.push(temp); for(int i=1;i<=3;i++){ if(x==xx[i]&&y==yy[i]&&!((opt>>(i-1))&1)) { In temp(opt+(1<<(i-1)),x,y+1,stp+times[opt+(1<<(i-1))]); if(dis[opt+(1<<(i-1))][x][y+1]>stp+times[opt+(1<<(i-1))]) dis[opt+(1<<(i-1))][x][y+1]=stp+times[opt+(1<<(i-1))],q.push(temp); } } } } else {//走路 if(x+1<=n&&chr[x+1][y]=='.') { In temp(opt,x+1,y,stp+times[opt]); if(dis[opt][x+1][y]>stp+times[opt]) dis[opt][x+1][y]=stp+times[opt],q.push(temp); } if(x-1>=1&&chr[x-1][y]=='.'){ In temp(opt,x-1,y,stp+times[opt]); if(dis[opt][x-1][y]>stp+times[opt]) dis[opt][x-1][y]=stp+times[opt],q.push(temp); } if(y-1>=1&&chr[x][y-1]=='.'){ In temp(opt,x,y-1,stp+times[opt]); if(dis[opt][x][y-1]>stp+times[opt]) dis[opt][x][y-1]=stp+times[opt],q.push(temp); } if(y+1<=m&&chr[x][y+1]=='.'){ In temp(opt,x,y+1,stp+times[opt]); if(dis[opt][x][y+1]>stp+times[opt]) dis[opt][x][y+1]=stp+times[opt],q.push(temp); } } } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%s",chr[i]+1); for(int i=0;i<=7;i++) for(int j=1;j<=n;j++) for(int k=1;k<=m;k++) dis[i][j][k]=inf; scanf("%d%d%d%d%d%d%d%d%d%d",&sx,&sy,&xx[1],&yy[1],&xx[2],&yy[2],&xx[3],&yy[3],&ex,&ey); scanf("%d%d%d",&t1,&t2,&t3); times[0]=1; times[1]=1+t1;times[2]=1+t2;times[3]=1+t1+t2; times[4]=1+t3;times[5]=1+t1+t3;times[6]=1+t2+t3;times[7]=1+t1+t2+t3; In tmp(0,sx,sy,0); q.push(tmp); dfs(); printf("%d\n",dis[7][ex][ey]); return 0; }
【H】
STL里面有个permutation,不过要自己手动写展开也不难。
#include<iostream> #include<cstdio> #include<cstring> #include<ctime> #include<cstdlib> #include<algorithm> #include<cmath> #include<string> using namespace std; int read(){ int xx=0,ff=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')ff=-1;ch=getchar();} while(ch>='0'&&ch<='9'){xx=(xx<<3)+(xx<<1)+ch-'0';ch=getchar();} return xx*ff; } int main(){ int num[8]={1,2,3,4,5,6,7,8}; do{ for(int i=0;i<7;i++) printf("%d ",num[i]); printf("%d",num[7]); puts(""); }while(next_permutation(num,num+8)); return 0; }
It is your time to fight!