比较大的坡度难度的课题,注册很水的问题,最难的问题是非常困难的;童鞋们也能看到一些问题进行了直接从主要采取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合分……)
版权声明:本文博客原创文章,博客,未经同意,不得转载。