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;
}
有沒有大佬能告訴我這是爲什麼??