11.24ACM补题
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int a,b,c,d; char mapp[51][51]; int main(){ scanf("%d%d%d%d",&a,&b,&c,&d); a--,b--; for(int i=1;i<=25;i++) for(int j=1;j<=50;j++) mapp[i][j]='a'; for(int i=26;i<=50;i++) for(int j=1;j<=50;j++) mapp[i][j]='b'; for(int i=1;i<=25;i+=2) for(int j=1;j<=50;j+=2) if(b)mapp[i][j]='b',b--; else if(c)mapp[i][j]='c',c--; else if(d)mapp[i][j]='d',d--; for(int i=27;i<=50;i+=2) for(int j=1;j<=50;j+=2) if(a)mapp[i][j]='a',a--; else if(c)mapp[i][j]='c',c--; else if(d)mapp[i][j]='d',d--; puts("50 50"); for(int i=1;i<=50;i++){ for(int j=1;j<=50;j++) cout<<mapp[i][j]; cout<<endl; } return 0; }
这就是其实很简单但就是想不出来的题目
如果考虑n和m的大小的话,我没有思路,一开始也因为这个放弃了
但其实题目没有要求n和m的大小,所以全部取最大50就行了
然后先分成上下ab两部分,然后在不改变原来连通块的基础上增加a,b,c,d的连通个数
需要注意一点就是——如果1到25行全部染成a,在26到50行增加a的连通时要从27开始。毕竟26染成a后可能和25行连通了
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; const int maxn=3007; struct edge{ int to,next; }e[maxn*10]; int n,m,head[maxn],a,b,tot,d[maxn],c[maxn][maxn]; void add(int u,int v){ e[tot].next=head[u]; e[tot].to=v; head[u]=tot++; } void init(){ c[0][0]=1; for(int i=1;i<maxn;i++){ c[i][0]=1; for(int j=1;j<=i;j++) c[i][j]=c[i-1][j]+c[i-1][j-1]; } } int main(){ init(); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) head[i]=-1; while(m--){ scanf("%d%d",&a,&b); add(a,b); } int ans=0; for(int u=1;u<=n;u++){ for(int i=head[u];i!=-1;i=e[i].next){ int v=e[i].to; for(int j=head[v];j!=-1;j=e[j].next){ int vv=e[j].to; if(vv==u)continue; d[vv]++; } } for(int i=1;i<=n;i++) if(d[i]){ ans+=c[d[i]][2]; d[i]=0; } } printf("%d\n",ans); return 0; }
比较简单的图论
起点u通过两个中转点v1,v2到达终点vv,问这种情况有多少种
其实就是遍历1到N作为起点,然后记录达到vv的个数,然后能找到多少种两条,答案就能增加多少
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; const int maxn=1007; struct edge{ int to,next,fro; }e[maxn*4]; int n,m,head[maxn],a,b,tot,v[maxn]; bool vis[maxn]; void add(int u,int v){ e[tot].next=head[u]; e[tot].to=v; e[tot].fro=u; head[u]=tot++; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&v[i]); while(m--){ scanf("%d%d",&a,&b); add(a,b),add(b,a); } int ans=0; for(int i=0;i<tot;i++) ans+=min(v[e[i].to],v[e[i].fro]); printf("%d\n",ans/2); return 0; }
同样很简单的图论
分析一下就能看出——剪每条边都必须要增加边两端其中一个顶点的值
而考虑剪法,其实就是选择两个顶点中值较小的那个
所以遍历一下边,增加两个顶点中值最小的就行了
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; const int maxn=307; int f[maxn],n,p[maxn],pos[maxn],ans[maxn]; char ch; int get(int x){ if(f[x]==x)return x; else return f[x]=get(f[x]); } void join(int a,int b){ int fa=get(a),fb=get(b); if(fa==fb)return; f[fa]=fb; } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&p[i]); pos[p[i]]=i; f[i]=i; } for(int i=1;i<=n;i++){ getchar(); for(int j=1;j<=n;j++){ ch=getchar(); if(ch=='1')join(i,j); } } for(int i=1;i<=n;i++){ int posi=pos[i]; for(int j=1;j<=n;j++) if(!ans[j]&&get(j)==get(posi)){ ans[j]=i; break; } } for(int i=1;i<=n;i++) cout<<ans[i]<<' '; cout<<endl; return 0; }
这也不算图论的题吧,就是将能转移的位置并在一起
然后让小的数尽量移动到前面去
#include<stdio.h> #include<iostream> #include<string.h> #include<algorithm> #include<set> using namespace std; typedef long long LL; const int maxn=500007; const int mod=1e9+7; int n,m,k,sz,num,f[maxn]; LL val[maxn],ans,t; set<int>st; struct edge{ int u,v; LL w; bool operator<(const edge &a)const{ return w<a.w; } }e[maxn]; int get(int x){ if(f[x]==x)return x; else return f[x]=get(f[x]); } void join(int a,int b){ int fa=get(a),fb=get(b); if(fa==fb)return; f[fa]=fb; st.insert(a);st.insert(b); sz--; } LL qow(LL a,int b){ LL ans=1; while(b){ if(b&1) ans=(ans*a)%mod; a=(a*a)%mod; b>>=1; } return ans; } int main(){ scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;i++) scanf("%lld",&val[i]); for(int i=0;i<m;i++){ scanf("%d%d",&e[i].u,&e[i].v); e[i].w=val[e[i].u]^val[e[i].v]; } sort(e,e+m); t=-1; for(int i=1;i<=n;i++) f[i]=i; for(int i=0;i<m;) if(e[i].w!=t){ t=e[i].w; num++;sz=n; while(e[i].w==t){ join(e[i].u,e[i].v); i++; } ans+=qow(2,sz); ans%=mod; for(auto it:st)f[it]=it; st.clear(); } ans+=((qow(2,k)+mod-num)%mod)*(qow(2,n)%mod); ans%=mod; printf("%lld\n",ans); return 0; }
这道题题目又长,异或又不熟,还有卡时间,是道很不友好的题
但是仔细分析后还是能做出来的
毕竟一个人总是喜欢把问题想复杂,这样他就可以放弃思考
而这道题,本质上也就是并查集加快速幂而已
如果你非要死磕建图,最后只会越想越复杂
所以做这道题学到的一点就是——当你怎么也想不出来的时候,最好换其他思路,不然会被饶进死圈子
然而即便我做过这道题,知道做法,还是TLE了几次
第一次TLE——恢复并起来的点的时候,不要直接1-n遍历恢复,而是将并过的点记录下来,然后恢复
第二次TLE——记录有多少个连通块的时候,从1-n找f[i]==i要找n次,而并过一次将sz--就比n次少很多,最后两个方法都能找到有多少连通块,不过第二种省时间能过
#include<stdio.h> #include<iostream> #include<string.h> #include<algorithm> using namespace std; typedef long long LL; const int maxn=2e5+7; int n,m,f[maxn],ans; bool cir[maxn]; struct Node{ int a,b,v; bool operator<(const Node &a)const{ return v>a.v; } }node[maxn]; int get(int x){ if(f[x]==x)return x; else return f[x]=get(f[x]); } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) scanf("%d%d%d",&node[i].a,&node[i].b,&node[i].v); sort(node+1,node+1+m); for(int i=1;i<=n;i++) f[i]=i; for(int i=1;i<=m;i++){ int fa=get(node[i].a),fb=get(node[i].b); if(fa!=fb){ if(cir[fa]&&cir[fb])continue; ans+=node[i].v; f[fa]=fb; cir[fb]=cir[fb]|cir[fa]; } else if(!cir[fa]){ ans+=node[i].v; cir[fa]=true; } } printf("%d\n",ans); return 0; }
啊这道题我也被骗了。
想着每个公主能选两个王子,不就是个容量为2的二分图匹配吗
如果是想找最多的找到王子的公主的个数的话,这个思路没错。但是公主有权值,要让权值最大,这个思路就不行了
然后再想会不会是什么 带权二分图匹配,不过网上查了没有这种做法
所以看了题解明白了这又是一道并查集的题
如果把公主i喜欢的王子ai,bi并在一起。那么一个非环连通块的点个数=边个数+1,表示n个公主在这n+1个王子中选到了n个王子。而如果是一个环,边数=点数,表示n个公主恰好分完了这n个王子。所以我们要做的,就是贪心+并查集。从权值大的公主开始遍历,如果她的两个王子在一个环里,那么显然她的两个王子已经被权值更大的公主选了。
但也有些需要注意的细节:
如果fa!=fb代表公主选的两个王子在不同的并查集里,但不代表她一定可以选一个,万一两个并查集都成环了呢.
同时fa!=fb,但其中一个已经成环,一个没有成环,两个并在一起,同样边数=点数,所以如果让f[fa]=fb,就让fb标记成环
#include<stdio.h> #include<iostream> #include<string.h> #include<vector> using namespace std; const int maxn=1e5+7; struct edge{ int to,next; }e[maxn]; int n,m,tot,head[maxn],d[maxn],pre[maxn],ans,maxlen,len; vector<int>b; void add(int u,int v){ e[tot].to=v; e[tot].next=head[u]; head[u]=tot++; } void solve(int u,int &len){ for(int i=head[u];i!=-1;i=e[i].next){ int v=e[i].to; if(d[v]>1)continue; pre[u]=v; len++; solve(v,len); } } void print(int id){ if(id==0)return; print(pre[id]); cout<<id<<' '; } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&m); if(m==1)b.push_back(i); } for(int i=1;i<=n;i++) head[i]=-1; for(int i=1;i<=n;i++){ scanf("%d",&m); if(m!=0)add(i,m),d[m]++; } for(int i=0;i<b.size();i++){ int u=b[i]; len=1; solve(u,len); if(len>maxlen){ maxlen=len; ans=u; } } cout<<maxlen<<endl; print(ans); return 0; }
这道题就比较简单了,要求从起点到终点过程中,不能有一个点能通向两个点。
所以可以事先记录每个点的出度。
然后将终点都记录下来,遍历终点找出最长的路径