第七次报告和标准的练习过程中解决
比较大的坡度难度的课题,注册很水的问题,最难的问题是非常困难的;童鞋们也能看到一些问题进行了直接从主要采取OJ的ACM称号,所以,总体来说难度可以说是大多数机器的问题和以前的练习。习的过题数计入平时成绩,能够看作一次超长时长的上机,终于board的情况能够说在意料之中,但也稍微超出了一点预估;希望成绩中没有非常大的水分。
A. 邀请函
难度中等偏下。
求有向图中一个点出发到全部点再返回的最短路之和。
我们注意到从一个点到全部点就是纯粹的单源最短路;而从全部点返回一个点。倘若我们把图中全部的边反向,就又变成了单源最短路。所以这道题的做法就是对原图和反图各求一遍单源最短路。这里请童鞋们自行区分一下反图和补图的概念。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int MAXN=1005; const int INF=0x3f3f3f3f; int g[MAXN][MAXN],dist[MAXN],n; bool vis[MAXN]; void dijkstra(int src) { memset(dist,0x3f,sizeof(dist)); memset(vis,false,sizeof(vis)); dist[src]=0; for(int i=1; i<=n; ++i) { pair<int,int> tmp=make_pair(INF,-1); for(int j=1; j<=n; ++j) if(!vis[j]&&dist[j]<tmp.first) tmp=make_pair(dist[j],j); if(!~tmp.second) break; vis[tmp.second]=true; for(int j=1; j<=n; ++j) dist[j]=min(dist[j],tmp.first+g[tmp.second][j]); } } int main() { int m,u,v,l; while(~scanf("%d%d",&n,&m)) { memset(g,0x3f,sizeof(g)); for(int i=1; i<=n; ++i) g[i][i]=0; while(m--) { scanf("%d%d%d",&u,&v,&l); g[u][v]=min(g[u][v],l); } int ans=0; dijkstra(1); for(int i=1; i<=n; ++i) ans+=dist[i]; for(int i=1; i<=n; ++i) for(int j=i+1; j<=n; ++j) swap(g[i][j],g[j][i]); dijkstra(1); for(int i=1; i<=n; ++i) ans+=dist[i]; printf("%d\n",ans); } }
#include<cstdio> #include<cstring> #include<queue> using namespace std; const int MAXN=1005; const int MAXM=100005; struct graph { int head[MAXN]; int to[MAXM]; int next[MAXM]; int len[MAXM]; int tot; void init() { tot=0; memset(head,0xff,sizeof(head)); } void add(int u,int v,int w) { to[tot]=v; len[tot]=w; next[tot]=head[u]; head[u]=tot++; } } g,rg; int dist[MAXN]; bool inque[MAXN]; void spfa(graph &G,int src) { memset(dist,0x3f,sizeof(dist)); memset(inque,false,sizeof(inque)); dist[src]=0; queue<int> q; q.push(src); inque[src]=true; while(!q.empty()) { int x=q.front(); q.pop(); inque[x]=false; for(int i=G.head[x]; ~i; i=G.next[i]) { int y=G.to[i]; if(dist[x]+G.len[i]<dist[y]) { dist[y]=dist[x]+G.len[i]; if(!inque[y]) { q.push(y); inque[y]=true; } } } } } int main() { int n,m,u,v,l; while(~scanf("%d%d",&n,&m)) { g.init(); rg.init(); while(m--) { scanf("%d%d%d",&u,&v,&l); g.add(u,v,l); rg.add(v,u,l); } int ans=0; spfa(g,1); for(int i=1; i<=n; ++i) ans+=dist[i]; spfa(rg,1); for(int i=1; i<=n; ++i) ans+=dist[i]; printf("%d\n",ans); } }
B. 寻找下界
这道题我预谋已久了……题目原打算直接起名叫lower_bound,后来不希望大家使用STL,故而改成不伦不类的中文名;最后干脆直接禁掉了可能利用lower_bound()函数的全部头文件。原因非常easy。二分尽管是每个程序员必须掌握的技巧,但又快又准地写好二分相同是一个学问,找一个序列中的某个数仅仅只是是最基础的操作罢了。
这道题,找的就是大于等于某个数的第一个数,同理,希望大家能自行练习寻找一个序列中大于某个数的第一个数(即upper_bound()函数)。话不多说,扔上三种姿势。
#include<cstdio> using namespace std; const int MAXN=100005; int a[MAXN],n; int lower_bound(int x) { int l=0,r=n; while(l<r) { int m=l+r>>1; x>a[m]?l=m+1:r=m; } return l; } int main() { int m,k; while(~scanf("%d%d",&n,&m)) { for(int i=0; i<n; ++i) scanf("%d",&a[i]); while(m--) { scanf("%d",&k); int idx=lower_bound(k); printf("%d\n",idx==n?-1:a[idx]); } } }
#include<cstdio> using namespace std; const int MAXN=100005; int a[MAXN],n; int lower_bound(int x) { int l=0,r=n-1; while(l<=r) { int m=l+r>>1; x>a[m]?l=m+1:r=m-1; } return l; } int main() { int m,k; while(~scanf("%d%d",&n,&m)) { for(int i=0; i<n; ++i) scanf("%d",&a[i]); while(m--) { scanf("%d",&k); int idx=lower_bound(k); printf("%d\n",idx==n?-1:a[idx]); } } }
#include<cstdio> #include<algorithm> using namespace std; const int MAXN=100005; int a[MAXN]; int main() { int n,m,k; while(~scanf("%d%d",&n,&m)) { for(int i=0; i<n; ++i) scanf("%d",&a[i]); while(m--) { scanf("%d",&k); int idx=lower_bound(a,a+n,k)-a; printf("%d\n",idx==n?-1:a[idx]); } } }
C. Cache
由于期末上机考试的临近,我们出了这道题,算是对链表操作的回想。这道题其意义在于考查了链表链节的合并和分离操作,倘若能够熟练处理这两个操作,事实上就具备了写块状链表的一切知识和技巧。块状链表不在课程要求内。但有心搞ACM的童鞋最好还是深入探究一下,自行查找资料并手动实现出块状链表的结构和功能。
由于有些童鞋说这道题用STL写各种RE,于是我就写了一个能够AC的STL版本号供參考。
#include<iostream> #include<string> #include<list> using namespace std; list<string> data; list<string>::iterator cur,tmp; int main() { int n,m,k; while(cin>>n>>m) { string str,op; while(n--) { cin>>str; data.push_back(str); } cur=data.begin(); while(m--) { cin>>op; switch(op[4]) { case 'R': ++cur; if(cur==data.end()) --cur; break; case 'L': if(cur!=data.begin()) --cur; break; case 'I': tmp=cur; ++tmp; if(tmp!=data.end()) { (*cur)+=(*tmp); data.erase(tmp); } break; case 'D': cin>>k; str=(*cur).substr(k,(*cur).size()-k); (*cur)=(*cur).substr(0,k); tmp=cur; ++tmp; data.insert(tmp,str); break; } } for(tmp=data.begin(); tmp!=data.end(); ++tmp) cout<<(*tmp)<<' '; cout<<'\n'<<(*cur)<<'\n'; data.clear(); } }
D. 行者无疆
从过题数能够看出。这是毫无疑问的最难题。大概一个多月前的一次训练赛,我和Thor合力搞了将近一个小时才做出来。只是当时那道题时限更严苛。后来我们总结了最短路的一些时间复杂度上的经验并写在了这篇blog里。我的做法是如果起点和终点也有充电桩,然后以每一个充电桩为起点跑一遍单源最短路,接着把全部的充电桩放在一起又一次建图,倘若之间的最短路径小于要求则有一条边。然后再从起点跑一遍单源最短路就可以。当然。这道题也能够直接改动dijkstra,理论上速度会更快,但显然从思考难度上又高了一些,这里不再讨论。
由于有两次建图且分别求了单源最短路。第一次是稀疏图,第二次是稠密图,所以我多写了一种用spfa处理第一次建图的写法,由于稀疏图下spfa相对于dijkstra的优势还是比較明显的,而稠密图下又会慢于dijkstra。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int MAXN=305; const int INF=0x3f3f3f3f; int city[MAXN][MAXN],pile[MAXN][MAXN],s[MAXN],dist[MAXN]; bool vis[MAXN]; void dijkstra(int g[][MAXN],int n,int src) { memset(dist,0x3f,sizeof(dist)); memset(vis,false,sizeof(vis)); dist[src]=0; for(int i=1; i<=n; ++i) { pair<int,int> tmp=make_pair(INF,-1); for(int j=1; j<=n; ++j) if(!vis[j]&&dist[j]<tmp.first) tmp=make_pair(dist[j],j); if(!~tmp.second) break; vis[tmp.second]=true; for(int j=1; j<=n; ++j) dist[j]=min(dist[j],tmp.first+g[tmp.second][j]); } } int main() { int n,m,v,k,x,y,d; while(~scanf("%d%d%d",&n,&m,&v)) { memset(city,0x3f,sizeof(city)); for(int i=1; i<=n; ++i) city[i][i]=0; memset(pile,0x3f,sizeof(pile)); while(m--) { scanf("%d%d%d",&x,&y,&d); if(d<city[x][y]) city[x][y]=city[y][x]=d; } scanf("%d",&k); for(int i=1; i<=k; ++i) scanf("%d",&s[i]); s[++k]=1; s[++k]=n; for(int i=1; i<=k; ++i) pile[i][i]=0; for(int i=1; i<=k; ++i) { dijkstra(city,n,s[i]); for(int j=i+1; j<=k; ++j) { int d=dist[s[j]]; if(d>v*5) d=INF; pile[i][j]=pile[j][i]=d; } } dijkstra(pile,k,k-1); printf("%d\n",dist[k]==INF?-1:dist[k]); } }
#include<cstdio> #include<cstring> #include<algorithm> #include<queue> using namespace std; const int MAXN=305; const int MAXM=20005; const int MAXK=55; const int INF=0x3f3f3f3f; struct graph { int head[MAXN]; int to[MAXM]; int next[MAXM]; int len[MAXM]; int tot; void init() { tot=0; memset(head,0xff,sizeof(head)); } void add(int u,int v,int w) { to[tot]=v; len[tot]=w; next[tot]=head[u]; head[u]=tot++; } } city; int dist[MAXN]; bool inque[MAXN]; void spfa(int src) { memset(dist,0x3f,sizeof(dist)); memset(inque,false,sizeof(inque)); dist[src]=0; queue<int> q; q.push(src); inque[src]=true; while(!q.empty()) { int x=q.front(); q.pop(); inque[x]=false; for(int i=city.head[x]; ~i; i=city.next[i]) { int y=city.to[i]; if(dist[x]+city.len[i]<dist[y]) { dist[y]=dist[x]+city.len[i]; if(!inque[y]) { q.push(y); inque[y]=true; } } } } } int pile[MAXK][MAXK],s[MAXK],k; bool vis[MAXK]; void dijkstra(int src) { memset(dist,0x3f,sizeof(dist)); memset(vis,false,sizeof(vis)); dist[src]=0; for(int i=1; i<=k; ++i) { pair<int,int> tmp=make_pair(INF,-1); for(int j=1; j<=k; ++j) if(!vis[j]&&dist[j]<tmp.first) tmp=make_pair(dist[j],j); if(!~tmp.second) break; vis[tmp.second]=true; for(int j=1; j<=k; ++j) dist[j]=min(dist[j],tmp.first+pile[tmp.second][j]); } } int main() { int n,m,v,x,y,d; while(~scanf("%d%d%d",&n,&m,&v)) { city.init(); while(m--) { scanf("%d%d%d",&x,&y,&d); city.add(x,y,d); city.add(y,x,d); } scanf("%d",&k); for(int i=1; i<=k; ++i) scanf("%d",&s[i]); s[++k]=1; s[++k]=n; memset(pile,0x3f,sizeof(pile)); for(int i=1; i<=k; ++i) pile[i][i]=0; for(int i=1; i<=k; ++i) { spfa(s[i]); for(int j=i+1; j<=k; ++j) { int d=dist[s[j]]; if(d>v*5) d=INF; pile[i][j]=pile[j][i]=d; } } dijkstra(k-1); printf("%d\n",dist[k]==INF?-1:dist[k]); } }
E. 多层礼包
水题,对栈的复习,不用栈也能做,不再多说。
#include<cstdio> #include<stack> using namespace std; char str[105]; int main() { while(~scanf("%s",&str)) { stack<char> s; for(int i=0; str[i]!='G'; ++i) switch(str[i]) { case '[': s.push(str[i]); break; case ']': if(!s.empty()) s.pop(); break; } printf("%d\n",s.size()); } }
#include<cstdio> using namespace std; char str[105]; int main() { while(~scanf("%s",&str)) { int ans=0; for(int i=0; str[i]!='G'; ++i) switch(str[i]) { case '[': ++ans; break; case ']': if(ans>0) --ans; break; } printf("%d\n",ans); } }
F. 无线网络
这道题的难度在于思路。
题目给出了全部点的坐标。然后非常多人就不知道这是一道什么类型的题了;实质上是给出了一个全然图。然后我们注意到。实现随意寝室的互联,并不须要直接互联。换言之,随意寝室的互联只是寻找一个最小生成树。
然后是怎样处理电力猫的问题,我们注意到电力猫相当于超级版的路由器。实现路由器的功能又不考虑距离,所以如果有k个电力猫,实质上我们是在找最小生成树的第k大边的长度。更具体的讨论能够看POJ原题的Discuss。更具体的证明须要讨论最小生成树和连通分量的关系,能够看这篇文章的第11~12页。此外,这道题是一个全然图。换言之是个非常稠密的图。用prim是比kruskal好得多的选择,尽管我控制了数据量让它们都能过。
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; const int MAXN=505; const int INF=0x3f3f3f3f; bool vis[MAXN]; double g[MAXN][MAXN],lowc[MAXN],len[MAXN]; int n,k,x[MAXN],y[MAXN]; inline int sqr(int x) { return x*x; } double prim() { memset(vis,false,sizeof(vis)); for(int i=1; i<=n; ++i) lowc[i]=g[1][i]; vis[1]=true; int cnt=0; for(int i=1; i<n; ++i) { int mark=-1; double minc=INF; for(int j=1; j<=n; ++j) if(!vis[j]&&minc>lowc[j]) { minc=lowc[j]; mark=j; } if(!~mark) return -1; len[cnt++]=minc; vis[mark]=true; for(int j=1; j<=n; ++j) if(!vis[j]&&lowc[j]>g[mark][j]) lowc[j]=g[mark][j]; } sort(len,len+cnt); for(int i=0;i<cnt;++i) printf("%.2f ",len[i]); putchar('\n'); return len[cnt-k]; } int main() { while(~scanf("%d%d",&n,&k)) { for(int i=1; i<=n; ++i) scanf("%d%d",&x[i],&y[i]); for(int i=1; i<=n; ++i) for(int j=i; j<=n; ++j) g[i][j]=g[j][i]=sqrt(sqr(x[i]-x[j])+sqr(y[i]-y[j])); printf("%.2f\n",prim()); } }
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; const int MAXN=505; const int MAXM=MAXN*MAXN; inline int sqr(int x) { return x*x; } struct edge { int u,v; double w; edge(int _u=0,int _v=0,double _w=0):u(_u),v(_v),w(_w) {} bool operator<(const edge &oth) const { return w<oth.w; } } e[MAXM]; int n,m,k,x[MAXN],y[MAXN],u[MAXN]; void init() { for(int i=1; i<=n; ++i) u[i]=i; } int find(int x) { if(u[x]!=x) u[x]=find(u[x]); return u[x]; } void merge(int x,int y) { u[find(x)]=find(y); } double kruskal() { if(n-k==0) return 0; sort(e,e+m); int cnt=0; init(); for(int i=0; i<m; ++i) if(find(e[i].u)!=find(e[i].v)) { merge(e[i].u,e[i].v); if(++cnt==n-k) return e[i].w; } } int main() { while(~scanf("%d%d",&n,&k)) { for(int i=1; i<=n; ++i) scanf("%d%d",&x[i],&y[i]); m=0; for(int i=1; i<=n; ++i) for(int j=i+1; j<=n; ++j) { double l=sqrt(sqr(x[i]-x[j])+sqr(y[i]-y[j])); e[m++]=edge(i,j,l); e[m++]=edge(j,i,l); } printf("%.2f\n",kruskal()); } }
G. barty的智商
首先说句题外话。barty是北航有史以来第一支也是眼下唯一一支ACM Final队的成员,是GG的队友,专攻图论。这道题也是以前的一道校赛原题,然而它并不难。它突出的思想是怎样把一个求解性问题转化成验证性问题。对于这道题。我们寻找满足要求的最低智商是困难的。但对于某一个智商值,我们推断它是否满足要求是easy的,仅仅要跑一遍拓扑排序就能够了。于是能够产生二分答案的思想,就是在智商值的范围内进行二分,对于每个值进行验证;二分过程和B题极其相似,也是在找一个下界(从而也能够看出对于二分,像书上单纯的查找某个值是否存在是最基础的,而B题的二分方式相同是有意义的)。
验证的时候能够选择建立新图,这里使用了vector模拟邻接表。
#include<cstdio> #include<cstring> #include<vector> #include<queue> using namespace std; const int MAXN=10005; const int INF=0x3f3f3f3f; vector<pair<int,int> > G[MAXN]; vector<int> g[MAXN]; int du[MAXN],n; bool toposort() { memset(du,0,sizeof(du)); for(int i=1; i<=n; ++i) for(int j=0; j<g[i].size(); ++j) ++du[g[i][j]]; int tot=0; queue<int> q; for(int i=1; i<=n; ++i) if(!du[i]) q.push(i); while(!q.empty()) { int u=q.front(); q.pop(); ++tot; for(int i=0; i<g[u].size(); ++i) { int v=g[u][i]; if(!(--du[v])) q.push(v); } } return tot==n; } int main() { int t,m,a,b,c; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); for(int i=1; i<=n; ++i) G[i].clear(); while(m--) { scanf("%d%d%d",&a,&b,&c); G[b].push_back(make_pair(a,c)); } int l=0,r=INF; while(l<r) { int m=l+r>>1; for(int i=1; i<=n; ++i) { g[i].clear(); for(int j=0; j<G[i].size(); ++j) if(G[i][j].second>m) g[i].push_back(G[i][j].first); } toposort()?r=m:l=m+1; } printf("%d\n",l); } }
也能够不建新图。对拓扑排序稍作修改。用前向星来写的邻接表。
#include<cstdio> #include<cstring> #include<queue> using namespace std; const int MAXN=10005; const int MAXM=10005; const int INF=0x3f3f3f3f; struct graph { int head[MAXN]; int to[MAXM]; int next[MAXM]; int len[MAXM]; int tot; void init() { tot=0; memset(head,0xff,sizeof(head)); } void add(int u,int v,int w=-1) { to[tot]=v; len[tot]=w; next[tot]=head[u]; head[u]=tot++; } } g; int du[MAXN],n; bool toposort(int k) { memset(du,0,sizeof(du)); for(int i=1; i<=n; ++i) for(int j=g.head[i]; ~j; j=g.next[j]) if(g.len[j]>k) ++du[g.to[j]]; int tot=0; queue<int> q; for(int i=1; i<=n; ++i) if(!du[i]) q.push(i); while(!q.empty()) { int u=q.front(); q.pop(); ++tot; for(int i=g.head[u]; ~i; i=g.next[i]) if(g.len[i]>k) { int v=g.to[i]; if(!(--du[v])) q.push(v); } } return tot==n; } int main() { int t,m,a,b,c; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); g.init(); while(m--) { scanf("%d%d%d",&a,&b,&c); g.add(a,b,c); } int l=0,r=INF; while(l<r) { int m=l+r>>1; toposort(m)?r=m:l=m+1; } printf("%d\n",l); } }
总的来说,我想给全部坚持独立完毕题目的童鞋点个赞。这次的题目可做性非常强,但也确实非常有难度,坚持做下来的人非常值得鼓舞。期末考试临近,希望大家好好复习重点的数据结构和算法,期末上机和笔试加油~(尽管上机是我们出题,写我们的外表似参加平地机or合分……)
版权声明:本文博客原创文章,博客,未经同意,不得转载。