140
T1 最小比例
Description
图中共有N个点的完全图,每条边都有权值,每个点也有权值。要求选出M个点和M-1条边,构成一棵树,使得:
即所有边的权值与所有点的权值之和的比率最小。
给定N和M,以及N个点的权值,和所有的边权,要求M个点的最小比率生成树。
Input
第一行包含两个整数N和M(2<=N<=15,2<=M<=N),表示点数和生成树的点数。
接下来一行N个整数,表示N个点的边权。
最后N行,每行N列,表示完全图中的边权。所有点权和边权都在[1,100]之间。
Output
输出最小比率生成树的M个点。当答案出现多种时,要求输出的第一个点的编号尽量小,第一个相同,则第二个点的编号尽量小,依次类推,中间用空格分开。编号从1开始。
Sample Input
输入1:
3 2
30 20 10
0 6 2
6 0 3
2 3 0
输入2:
2 2
1 1
0 2
2 0
Sample Output
输出1:
1 3
输出2:
1 2
Data Constraint
对于30%数据,N<=5。
Solution
N 很小,考虑搜索。
把选择的m个点用dfs枚举出来,再做最小生成树即可。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20;
const double INF = 2147483648.0;
int n,m;
double ANS=INF;
int wn[N],we[N][N],sum[N];
int ans[N],finalans[N];
int cnte,head[N],fa[N];
struct edge{
int u,v,w;
}e[N*N*2];
bool cmp(edge a,edge b){
return a.w<b.w;
}
void add(int u,int v,int w){
e[++cnte]=(edge){u, v, w};
head[u]=cnte;
}
int find(int x){
return x==fa[x] ? x : fa[x]=find(fa[x]);
}
int kruscal(){
int res=0,cnt=0;
cnte=0;
for(int i=1; i<=m; i++){
for(int j=1; j<=m; j++){
int u=ans[i],v=ans[j];
fa[u]=u;
add(u, v, we[u][v]);
add(v, u, we[v][u]);
}
}
sort(e+1, e+1+cnte, cmp);
for(int i=1; i<=cnte; i++){
int u=e[i].u,v=e[i].v,w=e[i].w;
int fu=find(u);
int fv=find(v);
if(fu!=fv){
fa[fv]=fu;
res+=w;
cnt++;
}
if(cnt==m-1){
break;
}
}
return res;
}
void dfs(int x,int y,int sum){
if(y+n-x+1<m || y>m){
return;
}
if(x==n+1){
int tmp=kruscal();
double ratio=(double)tmp/(double)sum;
if(ratio<ANS){
ANS=ratio;
memcpy(finalans, ans, sizeof finalans);
}
return;
}
ans[y+1]=x;
dfs(x+1, y+1, sum+wn[x]);
dfs(x+1, y, sum);
}
int main(){
freopen("ratio.in","r",stdin);
freopen("ratio.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++){
scanf("%d",&wn[i]);
}
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
scanf("%d",&we[i][j]);
sum[i]+=we[i][j];
}
}
dfs(1, 0, 0);
for(int i=1; i<=m; i++){
printf("%d ",finalans[i]);
}
return 0;
}
T2 软件公司
Description
一家软件开发公司有两个项目,并且这两个项目都由相同数量的m个子项目组成,对于同一个项目,每个子项目都是相互独立且工作量相当的,并且一个项目必须在m个子项目全部完成后才算整个项目完成。
这家公司有n名程序员分配给这两个项目,每个子项目必须由一名程序员一次完成,多名程序员可以同时做同一个项目中的不同子项目。
求最小的时间T使得公司能在T时间内完成两个项目。
Input
第一行两个正整数n,m(1<=n<=100,1<=m<=100)。
接下来n行,每行包含两个整数,x和y。分别表示每个程序员完成第一个项目的子程序的时间,和完成第二个项目子程序的时间。每个子程序耗时也不超过100。
Output
输出最小的时间T。
Sample Input
3 20
1 1
2 4
1 6
Sample Output
18
【样例解释】
第一个人做18个2项目,耗时18;第二个人做2个1项目,2个2项目耗时12;第三个人做18个1项目,耗时18。
Data Constraint
对于30%的数据,n<=30.
对于60%的数据,n<=60.
Solution
首先考虑朴素dp。
定义f[i][j][k]表示前i个人,1项目完成了j个,2项目完成了k个所需的最小时间。
容易得到:
取max是因为你可以和别人一起做工作。
复杂度,期望得分30pts,实际得分60pts。
考虑二分答案。
f[i][j][k]=1/0表示能否做到。
我们不需要枚举s和t了,因为枚举了一个,另外一个就可以算出来。
复杂度
期望得分60pts
继续优化。
f[i][j]表示前i个人,1项目做了j个,2项目能做的最多个数。
复杂度
期望得分100pts。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
const int INF = 0x3f3f3f3f;
int n,m;
int a[N],b[N];
int f[N][N];
bool check(int x){
for(int i=0; i<=n; i++){
for(int j=0; j<=m; j++){
f[i][j]=-INF;
}
}
f[0][0]=0;
for(int i=1; i<=n; i++){
for(int j=0; j<=m; j++){
for(int k=0; k<=j; k++){
if(k*a[i]>x){
break;
}
if((x-k*a[i])/b[i]>=0){
f[i][j]=max(f[i][j], f[i-1][j-k]+(x-k*a[i])/b[i]);
}
}
}
}
if(f[n][m]>=m){
return 1;
}
return 0;
}
int main(){
freopen("company.in","r",stdin);
freopen("company.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++){
scanf("%d%d",&a[i],&b[i]);
}
int l=1,r=100;
while(l<r){
int mid=(l+r)>>1;
if(check(mid)){
r=mid;
}
else{
l=mid+1;
}
}
printf("%d\n",l);
return 0;
}
T3 空间航行
Description
你是一艘战列巡洋舰的引擎操作人员,这艘船的船员在空间中侦测到了一些无法辨识的异常信号。你的指挥官给你下达了命令,让你制定航线,驾驶战列巡洋舰到达那里。
船上老旧的曲速引擎的速度是0.1AU/s。然而,在太空中分布着许多殖民星域,这些星域可以被看成一个球。在星域的内部,你可以在任何地方任意次跳跃到星域内部的任意一个点,不花费任何时间。
你希望算出到达终点的最短时间。
Input
输入包含多组测试数据。
对于每一组数据,第一行包含一个正整数n,表示殖民星域的数量。
接下来n 行,第i 行包含四个整数Xi,Yi,Zi,Ri,表示第i个星域的中心座标为(Xi, Yi,Zi),星域的半径是Ri。
接下来两行,第一行包含值Xa,Ya,Za,告诉你当前座标为(Xa, Ya,Za)。
第二行包含值Xo,Yo,Zo,告诉你目的地座标为(Xo, Yo,Zo)。
输入以一行单独的-1 结尾。所有座标的单位都是天文单位(AU)。
Output
对于每一组输入数据,输出一行表示从目前的位置到达指定目的地的最短时间,取整到最近整数。输入保证取整是明确的。
Sample Input
1
20 20 20 1
0 0 0
0 0 10
1
5 0 0 4
0 0 0
10 0 0
-1
Sample Output
100
20
Data Constraint
每个输入文件至多包含10 个测试数据。
对于10% 的数据,n = 0。
对于30% 的数据,0<=n<=10。
对于100% 的数据,0<=n<=100,所有座标的绝对值<=10000 ,半径r<=10000。
你可以认为,你所在的星区的大小为无限大。
Solution
对于两个中心分别为,,半径为,的星域,我们认为他们的距离是的。
特别地,对于起点和中点,我们将它们看做两个的星域。
对于每个点,向其他所有点建距离为权值的边,跑一遍最短路即可。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
using namespace std;
const int N = 110;
const double INF = 1000000000.0;
int n;
double get_dis(int x1,int y1,int z1,int x2,int y2,int z2){
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2));
}
struct node{
int x,y,z,r;
}a[N];
int cnte,head[N];
struct edge{
int to,nxt;
double w;
}e[N*N*2];
void add(int u,int v,double w){
e[++cnte].nxt=head[u];
e[cnte].to=v;
e[cnte].w=w;
head[u]=cnte;
}
double dis[N];
bool vis[N];
void spfa(){
queue<int> q;
q.push(0);
memset(vis, 0, sizeof vis);
for(int i=0; i<=n+1; i++){
dis[i]=INF;
}
dis[0]=0;
vis[0]=1;
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u]; i; i=e[i].nxt){
int v=e[i].to;
double w=e[i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(!vis[v]){
vis[v]=1;
q.push(v);
}
}
}
}
}
int main(){
freopen("warp.in","r",stdin);
freopen("warp.out","w",stdout);
while(scanf("%d",&n)){
cnte=0;
memset(head, 0, sizeof head);
memset(a, 0, sizeof a);
memset(e, 0, sizeof e);
if(n==-1){
break;
}
for(int i=1; i<=n; i++){
scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].z,&a[i].r);
}
scanf("%d%d%d",&a[0].x,&a[0].y,&a[0].z);
scanf("%d%d%d",&a[n+1].x,&a[n+1].y,&a[n+1].z);
for(int i=0; i<=n+1; i++){
for(int j=i+1; j<=n+1; j++){
double w=max(0.0, get_dis(a[i].x,a[i].y,a[i].z,a[j].x,a[j].y,a[j].z)-a[i].r-a[j].r);
add(i, j, w);
add(j, i, w);
}
}
spfa();
printf("%.0lf\n",dis[n+1]*10);
}
return 0;
}
T4 摧毁巴士站
Description
Gabiluso是最伟大的间谍之一。现在,他试图完成一个“不可能完成”的使命――减缓Colugu的军队到达机场的时间。Colugu有n个公共汽车站和m条道路。每条道路直接连接两个巴士站,所有的道路都是单向的。为了保持空气洁净,政府禁止所有军用车辆,因此,军队必须乘搭巴士去机场。两个巴士站之间,可能有超过一条道路。如果一个公共汽车站被破坏时,所有连接该站的道路将无法运作。Gabiluso需要做的是摧毁了一些公共汽车站,使军队无法在K分钟内到达机场。一辆公交车通过一条道路,都是一分钟。所有巴士站的编号从1到n。1号巴士站是在军营,第n号站是机场。军队始终从第一站出发。第一站和第n站不能被破坏,这里有大量的防御力量。当然也没有从第一站到第n站的道路。
请帮助Gabiluso来计算能完成使命所需摧毁的最低数目的巴士站。
Input
第一行包含三个整数n,m,k (2<n<=50,0<m<=4000,0<k<1000)。
接下来m行,每行2个整数s和f,表示从站s到站f有一条路。
Output
输出最少需要摧毁的巴士站数目。
Sample Input
5 7 3
1 3
3 4
4 5
1 2
2 5
1 4
4 5
Sample Output
2
Data Constraint
30%的数据N<=15。
Solution
我们进行如下操作:
1.跑最短路,处理出从1-n最短路径上的点集。如果,就尝试更新。
2.枚举点集中的点删除。
3.递归搜索。
复杂度分析:
无。
老师:这是算法。
你以为 ? 有这么大:
实际上只有这么大:
是不是觉得不太靠谱?
看看题解怎么说:
这样的搜索顺序使得枚举的点的数量得到控制。搜索的每一层会从L-2个点中选择一个进行删除,L是当前找到的最短路径的长度。如果当前找到的最短路径很长,虽然在这一层我们需要枚举很多个点进行删除,但也说明起点到终点的距离已经很长了,我们离答案已经很接近了。一般来说,在50个点的图中,答案应该不超过20。那么L<20。并且L比较大的情况只会出现在搜索的最后几层。这么想来,这个搜索的时间复杂度是可以接受的。
感觉靠谱了一点吗?
还可以使用迭代加深的技巧来优化(反向)这个算法。
非迭代加深: 48ms
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <ctime>
using namespace std;
const int N = 60;
const int M = 4010;
const int INF = 0x3f3f3f3f;
int n,m,k,lim,ans=INF;
int cnte,head[N];
struct edge{
int to,nxt;
}e[M];
void add(int u,int v){
e[++cnte]=(edge){v, head[u]};
head[u]=cnte;
}
int pre[N],dis[N];
bool vis[N],able[N];
void spfa(){
queue<int> q;
q.push(1);
memset(dis, 0x3f, sizeof dis);
memset(vis, 0, sizeof vis);
memset(pre, 0, sizeof pre);
vis[1]=1;
dis[1]=0;
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u]; i; i=e[i].nxt){
int v=e[i].to;
if(able[v] && dis[v]>dis[u]+1){
dis[v]=dis[u]+1;
pre[v]=u;
if(!vis[v]){
vis[v]=1;
q.push(v);
}
}
}
}
}
void dfs(int x){
if(x>=ans){
return;
}
spfa();
if(dis[n]>k){
ans=x;
return;
}
int now=n;
int tmp[N]={0},cnt=0;
while(now!=1){
now=pre[now];
tmp[++cnt]=now;
}
for(int i=1; i<cnt; i++){
able[tmp[i]]=0;
dfs(x+1);
able[tmp[i]]=1;
}
return;
}
int main(){
freopen("bus.in","r",stdin);
freopen("bus.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for(int i=1; i<=m; i++){
int u,v;
scanf("%d%d",&u,&v);
add(u, v);
}
memset(able, 1, sizeof able);
spfa();
if(dis[n]>k){
puts("0");
return 0;
}
memset(able, 1, sizeof able);
dfs(0);
printf("%d\n",ans);
return 0;
}
迭代加深: 83ms
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <ctime>
using namespace std;
const int N = 60;
const int M = 4010;
const int INF = 0x3f3f3f3f;
int n,m,k,lim,ans=INF;
int cnte,head[N];
struct edge{
int to,nxt;
}e[M];
void add(int u,int v){
e[++cnte]=(edge){v, head[u]};
head[u]=cnte;
}
int pre[N],dis[N];
bool vis[N],able[N];
void spfa(){
queue<int> q;
q.push(1);
memset(dis, 0x3f, sizeof dis);
memset(vis, 0, sizeof vis);
memset(pre, 0, sizeof pre);
vis[1]=1;
dis[1]=0;
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u]; i; i=e[i].nxt){
int v=e[i].to;
if(able[v] && dis[v]>dis[u]+1){
dis[v]=dis[u]+1;
pre[v]=u;
if(!vis[v]){
vis[v]=1;
q.push(v);
}
}
}
}
}
void dfs(int x){
if(x>=ans || x>=lim){
return;
}
spfa();
if(dis[n]>k){
ans=x;
return;
}
int now=n;
int tmp[N]={0},cnt=0;
while(now!=1){
now=pre[now];
tmp[++cnt]=now;
}
for(int i=1; i<cnt; i++){
able[tmp[i]]=0;
dfs(x+1);
able[tmp[i]]=1;
}
return;
}
int main(){
freopen("bus.in","r",stdin);
freopen("bus.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for(int i=1; i<=m; i++){
int u,v;
scanf("%d%d",&u,&v);
add(u, v);
}
memset(able, 1, sizeof able);
spfa();
if(dis[n]>k){
puts("0");
return 0;
}
int start,end;
for(lim=1; lim<n-1; lim++){
if(ans<=lim){
break;
}
memset(able, 1, sizeof able);
dfs(0);
}
printf("%d\n",ans);
return 0;
}
有没有大佬能告诉我这是为什么??