G - Cyclic Tour
題意:
圖中有n個點和m條有向邊
現在要將該圖分成若干環,每個環中至少有兩個點。環與環不能有交點。問所有環的總長度最小為多少?
題解: ( 最小環長度 + km )
可以發現,每個點的入度和出度都是1。
如果每個點都拆成入點和出點,對于點u,可以拆成u和u’, u是入點,u’是出點。
若有邊(u, v),則u’ -> v連邊
這樣整個圖轉化為一個二分圖。由于每個入點需要找一個出點連接,每個出點也要找一個入點連接,那么就是經典的二分圖匹配問題。加上一個權值,就是二分圖最優匹配問題。
#include <cstring>
#include <cstdio>
#include<algorithm>
using namespace std;
const int MAXN=105;
const int INF=0x3f3f3f3f;
int love[MAXN][MAXN];
int eg[MAXN];
int eb[MAXN];
int vg[MAXN];
int vb[MAXN];
int sl[MAXN];
int match[MAXN];
int n,m;
bool DFS(int girl)
{
vg[girl]=1;
for(int boy=0;boy<m;boy++)
{
if(vb[boy]) continue;
int gap=eg[girl]+eb[boy]-love[girl][boy];
if(gap==0)
{
vb[boy]=1;
if(match[boy]==-1||DFS(match[boy]))
{
match[boy]=girl;
return true;
}
}
else sl[boy]=min(sl[boy],gap);
}
return false;
}
int km()
{
memset(match,-1,sizeof(match));
memset(eb,0,sizeof(eb));
for(int i=0;i<n;i++)
{
eg[i]=love[i][0];
for(int j=1;j<m;j++)
{
eg[i]=max(eg[i],love[i][j]);
}
}
for(int i=0;i<n;i++)
{
fill(sl,sl+m,INF);
while(true)
{
memset(vg,0,sizeof(vg));
memset(vb,0,sizeof(vb));
if(DFS(i)) break;
int d=INF;
for(int j=0;j<m;j++)
{
if(!vb[j])d=min(d,sl[j]);
}
if(d==INF) return 1;
for(int j=0;j<n;j++)
{
if(vg[j]) eg[j]-=d;
}
for(int j=0;j<m;j++)
{
if(vb[j]) eb[j]+=d;
else sl[j]-=d;
}
}
}
int res=0,flag=0;
for(int j=0;j<m;j++)
{
if(match[j]==-1||love[match[j]][j]==-INF) continue;
res+=love[match[j]][j];
flag++;
}
if(flag<n) res=1;
return res;
}
int main()
{
int e;
int u,v,val;
while(scanf("%d%d",&n,&e)!=EOF)
{
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
love[i][j]=-INF;
}
}
for(int i=0;i<e;i++)
{
scanf("%d%d%d",&u,&v,&val);
if(-val>love[u-1][v-1]) love[u-1][v-1]=-val;
}
m=n;
printf("%d\n",-km());
}
return 0;
}
H - Tour
題意:
同G題一樣
#include <cstring>
#include <cstdio>
#include<algorithm>
using namespace std;
const int MAXN=210;
const int INF=0x3f3f3f3f;
int love[MAXN][MAXN];
int eg[MAXN];
int eb[MAXN];
int vg[MAXN];
int vb[MAXN];
int sl[MAXN];
int match[MAXN];
int n,m;
bool DFS(int girl)
{
vg[girl]=1;
for(int boy=0;boy<m;boy++)
{
if(vb[boy]) continue;
int gap=eg[girl]+eb[boy]-love[girl][boy];
if(gap==0)
{
vb[boy]=1;
if(match[boy]==-1||DFS(match[boy]))
{
match[boy]=girl;
return true;
}
}
else sl[boy]=min(sl[boy],gap);
}
return false;
}
int km()
{
memset(match,-1,sizeof(match));
memset(eb,0,sizeof(eb));
for(int i=0;i<n;i++)
{
eg[i]=love[i][0];
for(int j=1;j<m;j++)
{
eg[i]=max(eg[i],love[i][j]);
}
}
for(int i=0;i<n;i++)
{
fill(sl,sl+m,INF);
while(true)
{
memset(vg,0,sizeof(vg));
memset(vb,0,sizeof(vb));
if(DFS(i)) break;
int d=INF;
for(int j=0;j<m;j++)
{
if(!vb[j])d=min(d,sl[j]);
}
if(d==INF) return 1;
for(int j=0;j<n;j++)
{
if(vg[j]) eg[j]-=d;
}
for(int j=0;j<m;j++)
{
if(vb[j]) eb[j]+=d;
else sl[j]-=d;
}
}
}
int res=0,flag=0;
for(int j=0;j<m;j++)
{
if(match[j]==-1||love[match[j]][j]==-INF) continue;
res+=love[match[j]][j];
flag++;
}
if(flag<n) res=1;
return res;
}
int main()
{
int e;
int u,v,val;
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&e);
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
love[i][j]=-INF;
}
}
for(int i=0;i<e;i++)
{
scanf("%d%d%d",&u,&v,&val);
if(-val>love[u-1][v-1]) love[u-1][v-1]=-val;
}
m=n;
printf("%d\n",-km());
}
return 0;
}
I - A new Graph Game
題意:
求哈密頓回路上的最小權
題解:( 最小環長度 + km )
因為是無向哈密頓圖,所以每個點入度和出度必須為1,將每個點拆成u,u’,對于邊<u,v>,連接邊<u,v’>,<v,u’>;思路和G,H差不多
#include <cstring>
#include <cstdio>
#include<algorithm>
using namespace std;
const int MAXN=1010;
const int INF=0x3f3f3f3f;
int love[MAXN][MAXN];
int eg[MAXN];
int eb[MAXN];
int vg[MAXN];
int vb[MAXN];
int sl[MAXN];
int match[MAXN];
int n,m;
bool DFS(int girl)
{
vg[girl]=1;
for(int boy=0;boy<m;boy++)
{
if(vb[boy]) continue;
int gap=eg[girl]+eb[boy]-love[girl][boy];
if(gap==0)
{
vb[boy]=1;
if(match[boy]==-1||DFS(match[boy]))
{
match[boy]=girl;
return true;
}
}
else sl[boy]=min(sl[boy],gap);
}
return false;
}
int km()
{
memset(match,-1,sizeof(match));
memset(eb,0,sizeof(eb));
for(int i=0;i<n;i++)
{
eg[i]=love[i][0];
for(int j=1;j<m;j++)
{
eg[i]=max(eg[i],love[i][j]);
}
}
for(int i=0;i<n;i++)
{
fill(sl,sl+m,INF);
while(true)
{
memset(vg,0,sizeof(vg));
memset(vb,0,sizeof(vb));
if(DFS(i)) break;
int d=INF;
for(int j=0;j<m;j++)
{
if(!vb[j])d=min(d,sl[j]);
}
if(d==INF) return 1;
for(int j=0;j<n;j++)
{
if(vg[j]) eg[j]-=d;
}
for(int j=0;j<m;j++)
{
if(vb[j]) eb[j]+=d;
else sl[j]-=d;
}
}
}
int res=0,flag=0;
for(int j=0;j<m;j++)
{
if(match[j]==-1||love[match[j]][j]==-INF) continue;
res+=love[match[j]][j];
flag++;
}
if(flag<n) res=1;
return res;
}
int main()
{
int e;
int u,v,val;
int t;
scanf("%d",&t);
for(int cas=1;cas<=t;cas++)
{
scanf("%d%d",&n,&e);
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
love[i][j]=-INF;
}
}
for(int i=0;i<e;i++)
{
scanf("%d%d%d",&u,&v,&val);
if(-val>love[u-1][v-1])
{
love[u-1][v-1]=-val;
love[v-1][u-1]=-val;
}
}
m=n;
int res=-km();
if(res!=-1)
printf("Case %d: %d\n",cas,res);
else printf("Case %d: NO\n",cas);
}
return 0;
}
J - Card Game
題意:
就是給n個字符串,然后對于任意兩個字符串進行匹配,第一個倒序,第二個正序,找它們的最長公共前綴長度,就是它們的分數,自己和自己匹配分數為0,然后把這些字符串組成一些圈,求能得到的最大分數
題解: ( 最小環長度 + km )
因為是求出環,所以可以用匹配做;每個點都與另外所有的點相連,然后預處理求出連接兩點得到的分數;
#include <cstring>
#include <cstdio>
#include<algorithm>
using namespace std;
const int MAXN=205;
const int INF=0x3f3f3f3f;
int love[MAXN][MAXN];
int eg[MAXN];
int eb[MAXN];
int vg[MAXN];
int vb[MAXN];
int sl[MAXN];
int match[MAXN];
int n,m;
bool DFS(int girl)
{
vg[girl]=1;
for(int boy=0;boy<m;boy++)
{
if(vb[boy]) continue;
int gap=eg[girl]+eb[boy]-love[girl][boy];
if(gap==0)
{
vb[boy]=1;
if(match[boy]==-1||DFS(match[boy]))
{
match[boy]=girl;
return true;
}
}
else sl[boy]=min(sl[boy],gap);
}
return false;
}
int km()
{
memset(match,-1,sizeof(match));
memset(eb,0,sizeof(eb));
for(int i=0;i<n;i++)
{
eg[i]=love[i][0];
for(int j=1;j<m;j++)
{
eg[i]=max(eg[i],love[i][j]);
}
}
for(int i=0;i<n;i++)
{
fill(sl,sl+m,INF);
while(true)
{
memset(vg,0,sizeof(vg));
memset(vb,0,sizeof(vb));
if(DFS(i)) break;
int d=INF;
for(int j=0;j<m;j++)
{
if(!vb[j])d=min(d,sl[j]);
}
for(int j=0;j<n;j++)
{
if(vg[j]) eg[j]-=d;
}
for(int j=0;j<m;j++)
{
if(vb[j]) eb[j]+=d;
else sl[j]-=d;
}
}
}
int res=0;
for(int j=0;j<m;j++)
{
res+=love[match[j]][j];
}
return res;
}
int judge(char s1[], char s2[])
{
int res = 0;
for(int i = strlen(s1) - 1, j = 0;i>=0&&j<strlen(s2); i--, j++)
if(s1[i] != s2[j]) return res;
else res++;
return res;
}
int main()
{
char str[MAXN][1010];
while(~ scanf("%d", &n))
{
m=n;
for(int i = 0; i < n; i++)
scanf(" %s", str[i]);
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
if(i == j) love[i][j] = 0;
else love[i][j] = judge(str[i], str[j]);
printf("%d\n", km());
}
return 0;
}
K - Similarity
題意:
n,k,m表示序列長度為n,保證序列中最多有k種字母,有一種答案和m份試卷(每份試卷的字母可以隨意轉換,但是一種字母只能換成一種字母,同種字母),問其正確率最大可能為多少
題解:
求出每個字母轉換為其他字母后的答案的最大的正確數,即試卷的字母轉換為答案的字母的哪種方案使得正確總數最高;每個試卷字母u,轉換為每個答案字母v后的正確數為val,則在u,v間連接一條邊,graph[u][v]=val;最后就是求出二分圖的最大權;
這里參考別人的代碼才知道求出val的比較精妙的辦法是Θ(n)的復雜度
假設u轉換為v,那么在某個位置pos,可以存在paper[pos]=u,answer[pos]=v,那么這個位置就是有效的位置,所以graph[u][v]++;
( graph[u][v]表示paper中的所有u轉換為v后,可以和answer正確對上的答案總數比如paper為ABA,answer為CDC,假設A轉換為C,那么graph[A][C]=2;假設A轉換為D,那么grpah[A][D]=0 )
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=35;
const int INF=0x3f3f3f3f;
int love[MAXN][MAXN];
int eg[MAXN];
int eb[MAXN];
int vg[MAXN];
int vb[MAXN];
int sl[MAXN];
int match[MAXN];
int n,m;
bool DFS(int girl)
{
vg[girl]=1;
for(int boy=0;boy<m;boy++)
{
if(vb[boy]) continue;
int gap=eg[girl]+eb[boy]-love[girl][boy];
if(gap==0)
{
vb[boy]=1;
if(match[boy]==-1||DFS(match[boy]))
{
match[boy]=girl;
return true;
}
}
else sl[boy]=min(sl[boy],gap);
}
return false;
}
int km()
{
memset(match,-1,sizeof(match));
memset(eb,0,sizeof(eb));
for(int i=0;i<n;i++)
{
eg[i]=love[i][0];
for(int j=1;j<m;j++)
{
eg[i]=max(eg[i],love[i][j]);
}
}
for(int i=0;i<n;i++)
{
fill(sl,sl+m,INF);
while(true)
{
memset(vg,0,sizeof(vg));
memset(vb,0,sizeof(vb));
if(DFS(i)) break;
int d=INF;
for(int j=0;j<m;j++)
{
if(!vb[j])d=min(d,sl[j]);
}
for(int j=0;j<n;j++)
{
if(vg[j]) eg[j]-=d;
}
for(int j=0;j<m;j++)
{
if(vb[j]) eb[j]+=d;
else sl[j]-=d;
}
}
}
int res=0;
for(int j=0;j<m;j++)
{
if(match[j]==-1) continue;
res+=love[match[j]][j];
}
return res;
}
int main()
{
char answer[10010];
char paper[10010];
int t,len,k,e;
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d",&len,&k,&e);
for(int i=0;i<len;i++)
{
scanf(" %c",&answer[i]);
}
while(e--)
{
for(int i=0;i<len;i++)
{
scanf(" %c",&paper[i]);
}
memset(love,0,sizeof(love));
n=m=30;
for(int i=0;i<len;i++)
{
int u=paper[i]-'A';
int v=answer[i]-'A';
love[u][v]++;
}
printf("%.4f\n",km()*1.0/len);
}
}
return 0;
}
L - Mining Station on the Sea
題意:
海上有m個油田,n個港口和n條船,m>n,油田與油田之間有航道以及距離,港口與油田之間也有航道和距離。一開始n條船各自停在的油田中,現在船要開會港口,每個港口只能停一條船,求出開回港口的最短路徑。
題解: ( 最短路 + km )
船可以直接從原來位置開回港口,也可以先開到別的油田再開回港口,所以我們先求出每條船開回各個港口的最短路徑,也就是對每條船都進行一次單源最短路徑,得出船到港口的最短距離,然后再對每條船和每個港口建立二分圖,值是船i到港口j的最短路徑,然后就是二分圖最小權匹配了。注意:因為油田是中轉站,港口是終點站,所以油田之間的連邊是雙邊的,而港口和油田的來連邊是單向的(為了防止船從港口跑回油田)
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int MAXN=330;
const int MAXE=1000010;
const int INF=0x3f3f3f3f;
struct Node
{
int to,next,val;
};
Node edge[MAXE];
int head[MAXN],cnt;
void addEdge(int u,int v,int val)
{
edge[cnt].to=v;edge[cnt].val=val;
edge[cnt].next=head[u];head[u]=cnt++;
}
int dis[MAXN],inqueue[MAXN];
void spfa_bfs(int st)
{
memset(dis,INF,sizeof(dis));
memset(inqueue,0,sizeof(inqueue));
dis[st]=0;
int u,v,i;
queue<int> que;
que.push(st);
while(!que.empty())
{
u=que.front();
que.pop();
inqueue[u]=0;
for(i=head[u];i!=-1;i=edge[i].next)
{
v=edge[i].to;
if(dis[v]>dis[u]+edge[i].val)
{
dis[v]=dis[u]+edge[i].val;
if(!inqueue[v])
{
inqueue[v]=1;
que.push(v);
}
}
}
}
}
const int MAX=110;
int love[MAX][MAX],slack[MAX];
int visb[MAX],visg[MAX];
int eb[MAX],eg[MAX];
int n,m,match[MAX];
bool DFS(int girl)
{
visg[girl]=true;
for(int boy=1;boy<=n;boy++)
{
if(visb[boy]) continue;
int gap=eb[boy]+eg[girl]-love[girl][boy];
if(gap==0)
{
visb[boy]=1;
if(match[boy]==-1||DFS(match[boy]))
{
match[boy]=girl;
return true;
}
}
else slack[boy]=min(slack[boy],gap);
}
return false;
}
int km()
{
memset(eb,0,sizeof(eb));
memset(match,-1,sizeof(match));
for(int i=1;i<=n;i++)
{
eg[i]=love[i][1];
for(int j=2;j<=n;j++)
{
eg[i]=max(eg[i],love[i][j]);
}
}
for(int i=1;i<=n;i++)
{
memset(slack,INF,sizeof(slack));
while(true)
{
memset(visb,0,sizeof(visb));
memset(visg,0,sizeof(visg));
if(DFS(i)) break;
int d=INF;
for(int i=1;i<=n;i++)
{
if(!visb[i]) d=min(d,slack[i]);
}
for(int i=1;i<=n;i++)
{
if(visg[i]) eg[i]-=d;
if(visb[i]) eb[i]+=d;
else slack[i]-=d;
}
}
}
int res=0;
for(int i=1;i<=n;i++)
{
res+=love[match[i]][i];
}
return res;
}
int main()
{
int k,p,st[MAXN],u,v,val;
while(scanf("%d%d%d%d",&n,&m,&k,&p)!=EOF)
{
for(int i=1;i<=n;i++)
{
scanf("%d",&st[i]);
}
memset(head,-1,sizeof(head));
cnt=0;
for(int i=0;i<k;i++)
{
scanf("%d%d%d",&u,&v,&val);
addEdge(u,v,val);
addEdge(v,u,val);
}
for(int i=0;i<p;i++)
{
scanf("%d%d%d",&u,&v,&val);
addEdge(v,u+200,val);
}
for(int i=1;i<=n;i++)
{
spfa_bfs(st[i]);
for(int j=1;j<=n;j++)
{
love[i][j]=-dis[j+200];
}
}
printf("%d\n",-km());
}
}
M - Assignment
題意:
給出一個二分圖以及相應的權重,再給出一個原來的匹配,要求在修改原來的匹配最少的情況下求出最大權匹配。問修改的匹配數目是多少以及比原來的匹配增加了多少?
題解: ( km : 優先匹配原先的邊 )
首先,如果某條邊的最優匹配和原來的匹配一致,那么我們要優先選擇原來的那個匹配邊。
怎么做呢?
- 唯一的辦法就是增加原來的匹配邊的權重,這樣就可以優先匹配到原來的邊了。
但是,這樣做會不會使得匹配的最大權的值不正確?(可能偏大)
- 假設最多有n個點,先把原來的所有邊都乘以k倍(k>n),再對原來的匹配邊增加1,那么求出最大權后,得出的結果要除以k,因為n<k,而原來的匹配邊加1的得出的最大權-正確的最大權,就是原來的匹配邊加1所帶來的影響(這個影響最多增加n:當最大權匹配和原來匹配一致時),但是這個影響除以k就是0了,所以求出的最大權還是正確的;同時,因為原配邊加1,所以它會優先選擇原匹配邊了! woc!woc!
再來分析這樣一個問題:它與原來的匹配邊相比,修改了多少條匹配?
- 假設ans是最大權匹配結果(未除以k),因為原匹配邊加1,其他邊不變,所以ans%k就是原匹配邊的個數,那么改變的匹配數就是n-ans%k(從反面講,如果沒有一條邊加1,那么ans%k==0)
啊,蒟蒻只能學習別人的思路~~~
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
const int MAXN=60;
const int INF=0x7fffffff;
int eb[MAXN],eg[MAXN];
int match[MAXN];
int vb[MAXN],vg[MAXN];
int slack[MAXN];
int love[MAXN][MAXN];
int n,m,oldSum;
bool DFS(int girl)
{
vg[girl]=true;
for(int boy=1;boy<=m;boy++)
{
if(vb[boy]) continue;
int gap=eg[girl]+eb[boy]-love[girl][boy];
if(gap==0)
{
vb[boy]=1;
if(match[boy]==-1||DFS(match[boy]))
{
match[boy]=girl;
return true;
}
}
else slack[boy]=min(slack[boy],gap);
}
return false;
}
void KM()
{
memset(match,-1,sizeof(match));
memset(eb,0,sizeof(eb));
for(int i=1;i<=n;i++)
{
eg[i]=love[i][1];
for(int j=2;j<=m;j++)
{
eg[i]=max(eg[i],love[i][j]);
}
}
for(int i=1;i<=n;i++)
{
fill(slack+1,slack+1+m,INF);
while(true)
{
memset(vg,0,sizeof(vg));
memset(vb,0,sizeof(vb));
if(DFS(i)) break;
int d=INF;
for(int j=1;j<=m;j++)
{
if(!vb[j]) d=min(d,slack[j]);
}
for(int j=1;j<=n;j++)
{
if(vg[j]) eg[j]-=d;
}
for(int j=1;j<=m;j++)
{
if(vb[j]) eb[j]+=d;
else slack[j]-=d;
}
}
}
int res=0;
for(int i=1;i<=m;i++)
{
if(match[i]==-1) continue;
res+=love[match[i]][i];
}
printf("%d %d\n",n-res%100,res/100-oldSum/100);
}
int main()
{
int tmp;
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&love[i][j]);
love[i][j]*=100;
}
}
oldSum=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&tmp);
love[i][tmp]+=1;
oldSum+=love[i][tmp];
}
KM();
}
}
N - My Brute
題意:
有兩隊人,x隊和y隊,每隊都是n人,x隊每個人的攻擊力為ai,血量為pi;y隊每個人攻擊力bi,血量為hi。有一個計分數組v[n],假設x隊的第i個人和y隊某個隊員打架輸了,x隊扣分v[i];贏了,x隊積分v[i];打架的方式是x隊隊員先出手,y隊隊員扣血a,然后再到y出手,直到某個人血量<=0才結束,血量大于0的人贏。假設一開始的打架順序是x隊和y隊一一對應,即第一個對第一個....求出x隊的最大積分,并且求出和原匹配的匹配相似率。
題解:( km : 優先匹配原先的邊 )
建邊的時候模擬下打架就可以了,每個x隊隊員都與y隊隊員打架,建邊;
至于匹配相似率,其實M題一樣的做法。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=110;
const int INF=0x7fffffff;
int v[MAXN],h[MAXN],p[MAXN],a[MAXN],b[MAXN];
int eb[MAXN],eg[MAXN];
int match[MAXN];
int vb[MAXN],vg[MAXN];
int slack[MAXN];
int love[MAXN][MAXN];
int n,m;
bool DFS(int girl)
{
vg[girl]=true;
for(int boy=1;boy<=m;boy++)
{
if(vb[boy]) continue;
int gap=eg[girl]+eb[boy]-love[girl][boy];
if(gap==0)
{
vb[boy]=1;
if(match[boy]==-1||DFS(match[boy]))
{
match[boy]=girl;
return true;
}
}
else slack[boy]=min(slack[boy],gap);
}
return false;
}
void KM()
{
memset(match,-1,sizeof(match));
memset(eb,0,sizeof(eb));
for(int i=1;i<=n;i++)
{
eg[i]=0;
for(int j=1;j<=m;j++)
{
eg[i]=max(eg[i],love[i][j]);
}
}
for(int i=1;i<=n;i++)
{
fill(slack+1,slack+1+m,INF);
while(true)
{
memset(vg,0,sizeof(vg));
memset(vb,0,sizeof(vb));
if(DFS(i)) break;
int d=INF;
for(int j=1;j<=m;j++)
{
if(!vb[j]) d=min(d,slack[j]);
}
for(int j=1;j<=n;j++)
{
if(vg[j]) eg[j]-=d;
}
for(int j=1;j<=m;j++)
{
if(vb[j]) eb[j]+=d;
else slack[j]-=d;
}
}
}
int res=0;
for(int i=1;i<=m;i++)
{
res+=love[match[i]][i];
}
if(res<=0) printf("Oh, I lose my dear seaco!\n");
else printf("%d %.3lf%%\n",res/100,100.0*(res%100)/n);
}
int fight(int i,int j)
{
int hpi=h[i],hpj=p[j];//兩者血量
int vi=a[i],vj=b[j];//兩者攻擊
while(hpi && hpj)
{
hpj-=vi;
if(hpj<=0) return v[i];
hpi-=vj;
if(hpi<=0) return -v[i];
}
}
int main()
{
while(scanf("%d",&n)!=EOF,n)
{
m=n;
for(int i=1;i<=n;i++) scanf("%d",&v[i]);
for(int i=1;i<=n;i++) scanf("%d",&h[i]);
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
love[i][j]=fight(i,j);
love[i][j]*=100;
if(i==j) love[i][j]+=1;
}
}
KM();
}
}
R - Minimum Cost
題意:
有n個商店,m個倉庫,k種商品。給出每個商店需要的每種商品的數目,每個倉庫含有的每種商品的數目,以及每個商品 i從 倉庫 k 運到 商店 j 需要的費用,求出供貨費用最少的匹配
題解:( km + 拆點 )
先來看只有一種商品的情況,已知每個商店需要這種商品的數目以及每個倉庫的庫存,假設從某個倉庫運到某個商店,那么兩者連邊,問題是:有些商店不需要這種商品,有些倉庫沒有這種商品,而且一個倉庫的貨物不一定分給一個商店,這樣子就不符合二分圖了!
所以,我們可以對倉庫和商店進行拆點:假設倉庫i有n1個商品,那就把倉庫分成n1個點;假設商店j需要n2個商品,那就把商店分成n2個點,然后n1的每個點與n2的每個點連邊,邊權為費用。這樣子建圖就是二分圖了!倉庫的每個拆分點都和商店的每個拆分點連邊,然后求最小權匹配。
然后,對k種商品都這樣做,即可得k個商品的最小供貨費用。
ps:其實這道題可以拆點是因為題目說每種商品供,需都不大于3,所以才可以拆點,如果商品數目很大時,還是要用最大流來做。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=200;
const int INF=0x3f3f3f3f;
int love[MAXN][MAXN],slack[MAXN];
int visb[MAXN],visg[MAXN];
int eb[MAXN],eg[MAXN];
int match[MAXN],n,m;
bool DFS(int girl)
{
visg[girl]=true;
for(int boy=1;boy<=m;boy++)
{
if(visb[boy]) continue;
int gap=eb[boy]+eg[girl]-love[girl][boy];
if(gap==0)
{
visb[boy]=1;
if(match[boy]==-1||DFS(match[boy]))
{
match[boy]=girl;
return true;
}
}
else slack[boy]=min(slack[boy],gap);
}
return false;
}
int km()
{
memset(eb,0,sizeof(eb));
memset(match,-1,sizeof(match));
for(int i=1;i<=n;i++)
{
eg[i]=love[i][1];
for(int j=2;j<=m;j++)
{
eg[i]=max(love[i][j],eg[i]);
}
}
for(int i=1;i<=n;i++)
{
memset(slack,INF,sizeof(slack));
while(true)
{
memset(visb,0,sizeof(visb));
memset(visg,0,sizeof(visg));
if(DFS(i)) break;
int d=INF;
for(int i=1;i<=m;i++)
{
if(!visb[i]) d=min(d,slack[i]);
}
for(int i=1;i<=n;i++)
{
if(visg[i]) eg[i]-=d;
}
for(int i=1;i<=m;i++)
{
if(visb[i]) eb[i]+=d;
else slack[i]-=d;
}
}
}
int res=0;
for(int i=1;i<=m;i++)
{
if(match[i]==-1) continue;
res+=love[match[i]][i];
}
return res;
}
int kind[51][51][51];// i, j, k:一個 商品 i 從 倉庫 k 運到 商店 j 需要的費用
int shop[51][51];//商店 i,需要 貨物 j 的件數
int store[51][51];//倉庫 i 有 貨物 j 件數
int cpy[MAXN],cpx[MAXN];
int main()
{
int N,M,K;//商店 倉庫 商品種類
while(scanf("%d%d%d",&N,&M,&K)!=EOF,N+M+K)
{
for(int i=1;i<=N;i++)
{
for(int j=1;j<=K;j++)
{
scanf("%d",&shop[i][j]);
}
}
for(int i=1;i<=M;i++)
{
for(int j=1;j<=K;j++)
{
scanf("%d",&store[i][j]);
}
}
for(int i=1;i<=K;i++)
{
for(int j=1;j<=N;j++)
{
for(int k=1;k<=M;k++)
{
scanf("%d",&kind[i][j][k]);
}
}
}
bool flag=false;
for(int i=1;i<=K;i++)
{
int need=0,have=0;
for(int j=1;j<=N;j++) need+=shop[j][i];
for(int j=1;j<=M;j++) have+=store[j][i];
if(have<need)
{
flag=true;
break;
}
}
if(flag)
{
printf("-1\n");
continue;
}
int res=0;
for(int i=1;i<=K;i++)
{
int currx=0,curry=0;
for(int j=1;j<=N;j++)
{
for(int k=1;k<=shop[j][i];k++)
{
cpx[++currx]=j;
}
}
for(int j=1;j<=M;j++)
{
for(int k=1;k<=store[j][i];k++)
{
cpy[++curry]=j;
}
}
for(int j=1;j<=currx;j++)
{
for(int k=1;k<=curry;k++)
{
love[j][k]=-kind[i][cpx[j]][cpy[k]];
}
}
n=currx;m=curry;
res+=-km();
}
printf("%d\n",res);
}
}
T - The Windy's
題意:
有N個工件要在M個機器上加工,有一個N*M的矩陣描述其加工時間。
同一時間內每個機器只能加工一個工件,問加工完所有工件后,使得平均加工時間最小(等待的時間+加工的時間)。
題解: (拆點 + km )
假設某個機器處理了k個玩具,時間分別為a1,a2…..,ak
那么該機器耗費的時間為a1+(a1+a2)+(a1+a2+a3).......(a1+a2+...ak)
即a1*k + a2 * (k - 1) + a3 * (k - 2).... + ak
ai玩具在某個機器上倒數第k個處理,所耗費全局的時間為ai*k
對每個機器,最多可以處理n個玩具,拆成n個點,1~n分別代表某個玩具在這個機器上倒數第幾個被加工的,對于每個玩具i,機器j中拆的每個點k,連接一條w[i][j]*k權值的邊
神一般的題,蒟蒻只能看別人的思路!
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN = 3000;
const int INF = 0x3f3f3f3f;
int love[60][MAXN];
int ex_girl[MAXN];
int ex_boy[MAXN];
bool vis_girl[MAXN];
bool vis_boy[MAXN];
int match[MAXN];
int slack[MAXN];
int n,m;
bool dfs(int girl)
{
vis_girl[girl] = true;
for (int boy = 0; boy < m; ++boy) {
if (vis_boy[boy]) continue;
int gap = ex_girl[girl] + ex_boy[boy] - love[girl][boy];
if (gap == 0) {
vis_boy[boy] = true;
if (match[boy] == -1 || dfs( match[boy] )) {
match[boy] = girl;
return true;
}
} else {
slack[boy] = min(slack[boy], gap);
}
}
return false;
}
int km()
{
memset(match, -1, sizeof match);
memset(ex_boy, 0, sizeof ex_boy);
for (int i = 0; i < n; ++i) {
ex_girl[i] = love[i][0];
for (int j = 1; j < m; ++j) {
ex_girl[i] = max(ex_girl[i], love[i][j]);
}
}
for (int i = 0; i < n; ++i) {
fill(slack, slack + m, INF);
while (1) {
memset(vis_girl, false, sizeof vis_girl);
memset(vis_boy, false, sizeof vis_boy);
if (dfs(i)) break;
int d = INF;
for (int j = 0; j < m; ++j)
if (!vis_boy[j]) d = min(d, slack[j]);
if(d==INF) return -1;
for (int j = 0; j < n; ++j) {
if (vis_girl[j]) ex_girl[j] -= d;
}
for (int j = 0; j < m; ++j) {
if (vis_boy[j]) ex_boy[j] += d;
else slack[j] -= d;
}
}
}
int res = 0;
for(int i = 0; i < m; i++){
if(match[i]==-1)
continue;
res += love[match[i]][i];
}
return res;
}
int main()
{
int t,x,y,cost,cnt;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&x,&y);
for(int i=0;i<x;i++)
{
cnt=0;
for(int j=0;j<y;j++)
{
scanf("%d",&cost);
for(int k=1;k<=x;k++)
{
love[i][cnt++]=-k*cost;
}
}
}
n=x;m=x*y;
int sum=km();
printf("%.6f\n",-sum*1.0/n);
}
}