之前写过了关于普通二分匹配的相关题目了,就是寻找尽量多的边使得任意边连接的两点都没有与其他边相连,而km算法解决的则是在带权的二分图中寻找权值和最大的匹配,可以通过先给无连接的点连上权值为0或者负无穷(求最小权值和)的边,使得问题变成找到权值和最大的完美匹配。
简单来说,KM算法就是先限定好了最终的权值和然后寻找能不能在这个条件下找到完美匹配,给予左右两边的点一个顶标l的概念,只有l[x]+l[y]==w[x][y]的情况下,这条边此时才能进行匹配,一开始顶标的设置为:左边点的顶标为与该点相连的边的最大权值,右边点为0.若能找到完美匹配,则得到答案,否则我们就得修改顶标使得另一些原本不能用来匹配的边可以进行匹配,不过权值和比之前降低了。
裸题
#include
using namespace std;
typedef long long ll;
const int maxn=305;
const int inf=0x3f3f3f3f;
int n;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];
bool match(int i){
s[i]=1;
for(int j=1;j<=n;j++){
int cnt=lx[i]+ly[j]-w[i][j];
if(cnt==0&&!t[j]){
t[j]=1;
if(!matched[j]||match(matched[j])){
matched[j]=i;
return 1;
}
}
else{
slack[j]=min(slack[j],cnt);
}
}
return 0;
}
void update(){
int a=inf;
for(int i=1;i<=n;i++){
if(!t[i])a=min(a,slack[i]);
}
for(int i=1;i<=n;i++){
if(s[i])lx[i]-=a;
if(t[i])ly[i]+=a;
}
}
void km(){
memset(matched,0,sizeof(matched));
memset(lx,0,sizeof(lx));
memset(ly,0,sizeof(ly));
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
lx[i]=max(lx[i],w[i][j]);
}
}
for(int i=1;i<=n;i++){
memset(slack,0x3f,sizeof(slack));
while(1){
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
if(match(i))break;
else update();
}
}
}
int main()
{
while(~scanf("%d",&n)){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&w[i][j]);
}
}
km();
int ans=0;
for(int i=1;i<=n;i++){
ans+=lx[i];
ans+=ly[i];
}
printf("%d\n",ans);
}
return 0;
}
n个人n个家,给每个人分配一个home使得所有人回家需要的时间总和最小,通过bfs处理出二分图的边权取负之后km便可。
#include
using namespace std;
typedef long long ll;
const int maxn=105;
const int inf=0x3f3f3f3f;
int n,m;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];
bool match(int i){
s[i]=1;
for(int j=1;j<=n;j++){
int cnt=lx[i]+ly[j]-w[i][j];
if(cnt==0&&!t[j]){
t[j]=1;
if(!matched[j]||match(matched[j])){
matched[j]=i;
return 1;
}
}
else{
slack[j]=min(slack[j],cnt);
}
}
return 0;
}
void update(){
int a=inf;
for(int i=1;i<=n;i++){
if(!t[i])a=min(a,slack[i]);
}
for(int i=1;i<=n;i++){
if(s[i])lx[i]-=a;
if(t[i])ly[i]+=a;
}
}
void km(){
memset(matched,0,sizeof(matched));
memset(lx,0,sizeof(lx));
memset(ly,0,sizeof(ly));
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
lx[i]=max(lx[i],w[i][j]);
}
}
for(int i=1;i<=n;i++){
memset(slack,0x3f,sizeof(slack));
while(1){
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
if(match(i))break;
else update();
}
}
}
char ma[105][105];
int idd[105][105];
bool vis[105][105];
int dx[4]={1,0,0,-1};
int dy[4]={0,1,-1,0};
struct node{
int x,y,step;
};
void bfs(int id,int x,int y){
queue que;
que.push({x,y,0});
vis[x][y]=1;
while(!que.empty()){
node cnt=que.front();que.pop();
if(ma[cnt.x][cnt.y]=='m')w[id][idd[cnt.x][cnt.y]]=-cnt.step;
for(int i=0;i<4;i++){
int nx=cnt.x+dx[i],ny=cnt.y+dy[i];
if(nx>=0&&nx=0&&ny1;
que.push({nx,ny,cnt.step+1});
}
}
}
}
int main()
{
while(scanf("%d%d",&n,&m),n+m){
for(int i=0;iscanf("%s",ma[i]);
}
int tot=1;
for(int i=0;ifor(int j=0;jif(ma[i][j]=='m'){
idd[i][j]=tot++;
}
}
}
tot=1;
for(int i=0;ifor(int j=0;jif(ma[i][j]=='H'){
memset(vis,0,sizeof(vis));
bfs(tot++,i,j);
}
}
}
n=tot-1;
km();
int ans=0;
for(int i=1;i<=n;i++){
ans+=lx[i];ans+=ly[i];
}
printf("%d\n",-ans);
}
return 0;
}
学生给不同宿舍评价,分配宿舍给学生使得总得分最高,没有被i学生评分或评价为负数的宿舍不能分配给该学生,求能否给所有学生分配宿舍并求总分最高值。
把不能连的边权设为-inf,最后根据匹配边中有没有-inf的边就能简单判断是否有解了。
#include
using namespace std;
typedef long long ll;
const int maxn=505;
const int inf=0x2f2f2f2f;
int n,m,e;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];
bool match(int i){
s[i]=1;
for(int j=1;j<=n;j++){
int cnt=lx[i]+ly[j]-w[i][j];
if(cnt==0&&!t[j]){
t[j]=1;
if(!matched[j]||match(matched[j])){
matched[j]=i;
return 1;
}
}
else{
slack[j]=min(slack[j],cnt);
}
}
return 0;
}
void update(){
int a=inf;
for(int i=1;i<=n;i++){
if(!t[i])a=min(a,slack[i]);
}
for(int i=1;i<=n;i++){
if(s[i])lx[i]-=a;
if(t[i])ly[i]+=a;
}
}
void km(){
memset(matched,0,sizeof(matched));
for(int i=1;i<=n;i++){lx[i]=-inf;}
memset(ly,0,sizeof(ly));
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
lx[i]=max(lx[i],w[i][j]);
}
}
for(int i=1;i<=n;i++){
memset(slack,0x3f,sizeof(slack));
while(1){
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
if(match(i))break;
else update();
}
}
}
int main()
{
int a,b,c;
int cas=0;
while(~scanf("%d%d%d",&m,&n,&e)){
cas++;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
w[i][j]=-inf;
}
}
for(int i=0;iscanf("%d%d%d",&a,&b,&c);
a++;b++;
if(c>=0)
w[b][a]=c;
}
if(m>n){printf("Case %d: -1\n",cas);continue;}
km();
int ans=0;
for(int i=1;i<=m;i++){
int cnt=w[matched[i]][i];
if(cnt!=-inf){
ans+=cnt;
}
else{ans=-1;break;}
}
printf("Case %d: %d\n",cas,ans);
}
return 0;
}
D - Special Fish HDU - 3395
比较裸的km
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=105;
const int inf=0x3f3f3f3f;
int n,m,e;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];
bool match(int i){
s[i]=1;
for(int j=1;j<=n;j++){
int cnt=lx[i]+ly[j]-w[i][j];
if(cnt==0&&!t[j]){
t[j]=1;
if(!matched[j]||match(matched[j])){
matched[j]=i;
return 1;
}
}
else{
slack[j]=min(slack[j],cnt);
}
}
return 0;
}
void update(){
int a=inf;
for(int i=1;i<=n;i++){
if(!t[i])a=min(a,slack[i]);
}
for(int i=1;i<=n;i++){
if(s[i])lx[i]-=a;
if(t[i])ly[i]+=a;
}
}
void km(){
memset(matched,0,sizeof(matched));
memset(lx,0,sizeof(lx));
memset(ly,0,sizeof(ly));
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
lx[i]=max(lx[i],w[i][j]);
}
}
for(int i=1;i<=n;i++){
memset(slack,0x3f,sizeof(slack));
while(1){
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
if(match(i))break;
else update();
}
}
}
int val[105];
int main()
{
char str[105];
while(~scanf("%d",&n),n){
memset(w,0,sizeof(w));
for(int i=1;i<=n;i++){scanf("%d",&val[i]);}
for(int i=0;iscanf("%s",str);
for(int j=0;jif(str[j]=='1')w[i+1][j+1]=val[i+1]^val[j+1];
}
}
km();
int ans=0;
for(int i=1;i<=n;i++){
ans+=lx[i];ans+=ly[i];
}
printf("%d\n",ans);
}
return 0;
}
巧克力可以移动到相邻的盒子里,求使所有盒子里都只有不多于一个巧克力所需要的最小移动次数。
处理巧克力与所有盒子的距离建边取负即可,求距离需要注意。。
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=505;
const int inf=0x2f2f2f2f;
int n,m,e;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];
//w cal wa,lx's init wa
bool match(int i){
s[i]=1;
for(int j=1;j<=n;j++){
int cnt=lx[i]+ly[j]-w[i][j];
if(cnt==0&&!t[j]){
t[j]=1;
if(!matched[j]||match(matched[j])){
matched[j]=i;
return 1;
}
}
else{
slack[j]=min(slack[j],cnt);
}
}
return 0;
}
void update(){
int a=inf;
for(int i=1;i<=n;i++){
if(!t[i])a=min(a,slack[i]);
}
for(int i=1;i<=n;i++){
if(s[i])lx[i]-=a;
if(t[i])ly[i]+=a;
}
}
void km(){
memset(matched,0,sizeof(matched));
// memset(lx,0,sizeof(lx));
for(int i=1;i<=n;i++)lx[i]=-inf;
memset(ly,0,sizeof(ly));
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
lx[i]=max(lx[i],w[i][j]);
}
}
for(int i=1;i<=n;i++){
memset(slack,0x3f,sizeof(slack));
while(1){
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
if(match(i))break;
else update();
}
}
}
int num[505];
int main()
{
while(~scanf("%d",&n)){
int tot=1;
memset(w,0,sizeof(w));
for(int i=1;i<=n;i++){
scanf("%d",&num[i]);
}
for(int i=1;i<=n;i++){
int cnt=num[i];
for(int j=1;j<=cnt;j++){
for(int k=1;k<=n;k++){
w[tot][k]=-min(abs(k-i),n-abs(i-k));
}
tot++;
}
}
km();
int ans=0;
for(int i=1;i<=n;i++){
ans+=lx[i],ans+=ly[i];
}
printf("%d\n",-ans);
}
return 0;
}
裸题,处理名字转化为编号进行建边就行,我用的map处理
#include
using namespace std;
typedef long long ll;
const int maxn=205;
const int inf=0x3f3f3f3f;
int n,m,k;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];
//slack的更新错误
bool match(int i){
s[i]=1;
for(int j=1;j<=m;j++){
int cnt=lx[i]+ly[j]-w[i][j];
if(cnt==0&&!t[j]){
t[j]=1;
if(!matched[j]||match(matched[j])){
matched[j]=i;
return 1;
}
}
else{
slack[j]=min(slack[j],cnt);
}
}
return 0;
}
void update(){
int a=inf;
for(int i=1;i<=m;i++){
if(!t[i])a=min(a,slack[i]);
}
for(int i=1;i<=n;i++){
if(s[i])lx[i]-=a;
}
for(int i=1;i<=m;i++){
if(t[i])ly[i]+=a;
}
}
void km(){
memset(matched,0,sizeof(matched));
memset(lx,-inf,sizeof(lx));
memset(ly,0,sizeof(ly));
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
lx[i]=max(lx[i],w[i][j]);
}
}
for(int i=1;i<=n;i++){
memset(slack,0x3f,sizeof(slack));
while(1){
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
if(match(i))break;
else update();
}
}
}
int main()
{
while(~scanf("%d%d%d",&n,&m,&k)){
memset(w,-inf,sizeof(w));
char na[25],nb[25];
int tota=1,totb=1,c;
map<string,int> ma;
for(int i=0;iscanf("%s%s%d",na,nb,&c);
if(ma.find(na)==ma.end())ma[na]=tota++;
if(ma.find(nb)==ma.end())ma[nb]=totb++;
int cnta=ma[na],cntb=ma[nb];
w[cnta][cntb]=-c;
}
km();
int ans=0;
for(int i=1;i<=m;i++){
if(matched[i]&&matched[i]<=n){
ans+=w[matched[i]][i];
}
}
printf("%d\n",-ans);
}
}
寻找环使得每个点都被访问过且路径权值和最小
只要每个点的入度和出度都为1就可以成环了,注意重边。
#include
using namespace std;
typedef long long ll;
const int maxn=105;
const int inf=0x3f3f3f3f;
int n,m,k;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];
int head[maxn];
//cheng huan tiao jian cuowu && chongbian
bool match(int i){
s[i]=1;
for(int j=1;j<=m;j++){
int cnt=lx[i]+ly[j]-w[i][j];
if(cnt==0&&!t[j]){
t[j]=1;
if(!matched[j]||match(matched[j])){
matched[j]=i;
head[j]=head[i];
return 1;
}
}
else{
slack[j]=min(slack[j],cnt);
}
}
return 0;
}
void update(){
int a=inf;
for(int i=1;i<=m;i++){
if(!t[i])a=min(a,slack[i]);
}
for(int i=1;i<=n;i++){
if(s[i])lx[i]-=a;
}
for(int i=1;i<=m;i++){
if(t[i])ly[i]+=a;
}
}
void km(){
memset(matched,0,sizeof(matched));
memset(lx,-0x3f,sizeof(lx));
memset(ly,0,sizeof(ly));
for(int i=1;i<=n;i++){
head[i]=i;
for(int j=1;j<=m;j++){
lx[i]=max(lx[i],w[i][j]);
}
}
for(int i=1;i<=n;i++){
memset(slack,0x3f,sizeof(slack));
while(1){
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
if(match(i))break;
else update();
}
}
}
int main()
{
int a,b,c;
while(~scanf("%d%d",&n,&k)){
m=n;
memset(w,-0x3f,sizeof(w));
for(int i=0;iscanf("%d%d%d",&a,&b,&c);
if(-c>w[a][b])
w[a][b]=-c;
}
km();
int ans=0;
for(int i=1;i<=n;i++){
int cnt=w[matched[i]][i];
if(cnt==w[0][0]){ans=1;break;}
ans+=cnt;
}
printf("%d\n",-ans);
}
}
跟上题一样
#include
using namespace std;
typedef long long ll;
const int maxn=205;
const int inf=0x3f3f3f3f;
int n,m,k;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];
int head[maxn];
bool match(int i){
s[i]=1;
for(int j=1;j<=m;j++){
int cnt=lx[i]+ly[j]-w[i][j];
if(cnt==0&&!t[j]){
t[j]=1;
if(!matched[j]||match(matched[j])){
matched[j]=i;
head[j]=head[i];
return 1;
}
}
else{
slack[j]=min(slack[j],cnt);
}
}
return 0;
}
void update(){
int a=inf;
for(int i=1;i<=m;i++){
if(!t[i])a=min(a,slack[i]);
}
for(int i=1;i<=n;i++){
if(s[i])lx[i]-=a;
}
for(int i=1;i<=m;i++){
if(t[i])ly[i]+=a;
}
}
void km(){
memset(matched,0,sizeof(matched));
memset(lx,-0x3f,sizeof(lx));
memset(ly,0,sizeof(ly));
for(int i=1;i<=n;i++){
head[i]=i;
for(int j=1;j<=m;j++){
lx[i]=max(lx[i],w[i][j]);
}
}
for(int i=1;i<=n;i++){
memset(slack,0x3f,sizeof(slack));
while(1){
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
if(match(i))break;
else update();
}
}
}
int main()
{
int a,b,c;
int t;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&k);
m=n;
memset(w,-0x3f,sizeof(w));
for(int i=0;iscanf("%d%d%d",&a,&b,&c);
if(-c>w[a][b])
w[a][b]=-c;
}
km();
int ans=0;
for(int i=1;i<=n;i++){
int cnt=w[matched[i]][i];
if(cnt==w[0][0]){ans=1;break;}
ans+=cnt;
}
printf("%d\n",-ans);
}
}
找哈密顿图,使用的代码跟上面两题一样,但感觉哈密顿图不应该只有一个环吗?
也是比较裸的题,按题意处理边权即可。
#include
using namespace std;
typedef long long ll;
const int maxn=1006;
const int inf=0x3f3f3f3f;
int n,m,k;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];
//selfloop always equals to 0 && lower mistake
bool match(int i){
s[i]=1;
for(int j=1;j<=m;j++){
int cnt=lx[i]+ly[j]-w[i][j];
if(cnt==0&&!t[j]){
t[j]=1;
if(!matched[j]||match(matched[j])){
matched[j]=i;
return 1;
}
}
else{
slack[j]=min(slack[j],cnt);
}
}
return 0;
}
void update(){
int a=inf;
for(int i=1;i<=m;i++){
if(!t[i])a=min(a,slack[i]);
}
for(int i=1;i<=n;i++){
if(s[i])lx[i]-=a;
}
for(int i=1;i<=m;i++){
if(t[i])ly[i]+=a;
}
}
void km(){
memset(matched,0,sizeof(matched));
memset(lx,0,sizeof(lx));
memset(ly,0,sizeof(ly));
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
lx[i]=max(lx[i],w[i][j]);
}
}
for(int i=1;i<=n;i++){
memset(slack,0x3f,sizeof(slack));
while(1){
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
if(match(i))break;
else update();
}
}
}
char str[205][1001];
int cal(char *a,char *b){
int lena=strlen(a);
int lenb=strlen(b);
int i=0;
for(;iif(b[i]!=a[lena-1-i])break;
}
return i;
}
int main()
{
while(~scanf("%d",&n)){
m=n;
memset(w,0,sizeof(w));
for(int i=1;i<=n;i++)scanf("%s",str[i]);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j)w[i][j]=0;
else w[i][j]=cal(str[i],str[j]);
}
}
km();
int ans=0;
for(int i=1;i<=n;i++){
ans+=lx[i],ans+=ly[i];
}
printf("%d\n",ans);
}
}
对点分类,每个人分类用的编号不同,求两组答案最大的相似度为多少,也是处理建边就行。用map似乎超时?直接开[26][26]的数组做就行。
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 30;
const int inf = 0x3f3f3f3f;
int n, m, k, m1;
int w[maxn][maxn];
int lx[maxn], ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn], t[maxn];
bool match(int i){
s[i] = 1;
for (int j = 1; j <= m; j++){
int cnt = lx[i] + ly[j] - w[i][j];
if (cnt == 0 && !t[j]){
t[j] = 1;
if (!matched[j] || match(matched[j])){
matched[j] = i;
return 1;
}
}
else{
slack[j] = min(slack[j], cnt);
}
}
return 0;
}
void update(){
int a = inf;
for (int i = 1; i <= m; i++){
if (!t[i])a = min(a, slack[i]);
}
for (int i = 1; i <= n; i++){
if (s[i])lx[i] -= a;
}
for (int i = 1; i <= m; i++){
if (t[i])ly[i] += a;
}
}
void km(){
memset(matched, 0, sizeof(matched));
memset(lx, 0, sizeof(lx));
memset(ly, 0, sizeof(ly));
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
lx[i] = max(lx[i], w[i][j]);
}
}
for (int i = 1; i <= n; i++){
memset(slack, 0x3f, sizeof(slack));
while (1){
memset(s, 0, sizeof(s));
memset(t, 0, sizeof(t));
if (match(i))break;
else update();
}
}
}
int s1[10005];
int main()
{
int t;
scanf("%d", &t);
while (t--){
scanf("%d%d%d", &n, &k, &m1);
int aass=n;
double n1 = n;
int tot1 = 0, tot2;
char c[2];
for (int i = 0; iscanf("%s", c);
char cnt = c[0];
s1[i] =cnt-'A'+1;
}
for (int i = 0; imemset(w, 0, sizeof(w));
tot2 = 0;
for (int j = 0; jscanf("%s", c);
char cnt = c[0];
w[s1[j]][cnt - 'A'+1]++;
}
n = m = 26;
km();
double ans = 0;
for (int j = 1; j <= m; j++){
if (matched[j]){
ans += w[matched[j]][j];
}
}
ans = ans / n1;
printf("%.4lf\n", ans);
}
}
return 0;
}
船返回港口的问题,用floyd处理出所有点对间的最短路来建边,需要注意的是船入港后不能再驶出,所以从港口连出的边边权得为inf,否则会有船的最短路是经过港口的。
#include
using namespace std;
typedef long long ll;
const int maxn = 105;
const int inf = 0x3f3f3f3f;
int n, m, m1, k, p;
int w[maxn][maxn];
int lx[maxn], ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn], t[maxn];
//入港口后就不能出港口了,dis的更新错误
bool match(int i){
s[i] = 1;
for (int j = 1; j <= m; j++){
int cnt = lx[i] + ly[j] - w[i][j];
if (cnt == 0 && !t[j]){
t[j] = 1;
if (!matched[j] || match(matched[j])){
matched[j] = i;
return 1;
}
}
else{
slack[j] = min(slack[j], cnt);
}
}
return 0;
}
void update(){
int a = inf;
for (int i = 1; i <= m; i++){
if (!t[i])a = min(a, slack[i]);
}
for (int i = 1; i <= n; i++){
if (s[i])lx[i] -= a;
}
for (int i = 1; i <= m; i++){
if (t[i])ly[i] += a;
}
}
void km(){
memset(matched, 0, sizeof(matched));
memset(lx, -0x3f, sizeof(lx));
memset(ly, 0, sizeof(ly));
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
lx[i] = max(lx[i], w[i][j]);
}
}
for (int i = 1; i <= n; i++){
memset(slack, 0x3f, sizeof(slack));
while (1){
memset(s, 0, sizeof(s));
memset(t, 0, sizeof(t));
if (match(i))break;
else update();
}
}
}
int id[105];
int dis[305][305];
int main()
{
int a,b,c;
while(~scanf("%d%d%d%d",&n,&m1,&k,&p)){
m=n;
memset(w,-0x3f,sizeof(w));
memset(dis,0x3f,sizeof(dis));
for(int i=0;iscanf("%d",&id[i]);
id[i]--;
}
for(int i=0;iscanf("%d%d%d",&a,&b,&c);
a--,b--;
dis[a][b]=min(dis[a][b],c);
dis[b][a]=dis[a][b];
}
for(int i=0;iscanf("%d%d%d",&a,&b,&c);
a--;b--;
// dis[m1+a][b]=min(dis[m1+a][b],c);
dis[b][m1+a]=min(dis[b][m1+a],c);
}
for(int k=0;kfor(int i=0;ifor(int j=0;jfor(int i=0;iint cnt=id[i];
for(int j=0;j1][j+1]=-dis[cnt][m1+j];
}
}
km();
int ans=0;
for(int i=1;i<=n;i++){
ans+=lx[i],ans+=ly[i];
}
printf("%d\n",-ans);
}
return 0;
}
题意看起来就是很裸的最大边权二分匹配,但是要尽量不改变原来的匹配,所以需要 对边权方法然后使原匹配边的边权+1,这样就会尽量不改变原匹配边了。
#include
using namespace std;
typedef long long ll;
const int maxn = 55;
const int inf = 0x3f3f3f3f;
int n, m, m1, k, p;
int w[maxn][maxn];
int lx[maxn], ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn], t[maxn];
int chose[maxn];
//需要放大权值并将原匹配边的权值+1,使得尽量匹配元匹配边
bool match(int i){
s[i] = 1;
for (int j = 0; j <= m; j++){
int cnt = lx[i] + ly[j] - w[i][j];
if (cnt == 0 && !t[j]){
t[j] = 1;
if (!matched[j] || match(matched[j])){
matched[j] = i;
return 1;
}
}
else{
slack[j] = min(slack[j], cnt);
}
}
return 0;
}
void update(){
int a = inf;
for (int i = 1; i <= m; i++){
if (!t[i])a = min(a, slack[i]);
}
for (int i = 1; i <= n; i++){
if (s[i])lx[i] -= a;
}
for (int i = 1; i <= m; i++){
if (t[i])ly[i] += a;
}
}
void km(){
memset(matched, 0, sizeof(matched));
memset(lx, 0, sizeof(lx));
memset(ly, 0, sizeof(ly));
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
lx[i] = max(lx[i], w[i][j]);
}
}
for (int i = 1; i <= n; i++){
memset(slack, 0x3f, sizeof(slack));
while (1){
memset(s, 0, sizeof(s));
memset(t, 0, sizeof(t));
if (match(i))break;
else update();
}
}
}
int main()
{
while(~scanf("%d%d",&n,&m)){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&w[i][j]);
w[i][j]*=200;
}
}
for(int i=1;i<=n;i++){
scanf("%d",&chose[i]);
w[i][chose[i]]++;
}
int old=0;
for(int i=1;i<=n;i++){
old+=w[i][chose[i]]/200;
}
km();
int ans=0;
int num=0;
for(int i=1;i<=m;i++){
if(matched[i]){
if(chose[matched[i]]!=i)num++;
ans+=w[matched[i]][i]/200;
}
}
ans-=old;
printf("%d %d\n",num,ans);
}
return 0;
}
跟上面那题差不多的操作。
#include
using namespace std;
typedef long long ll;
const int maxn = 100;
const int inf = 0x3f3f3f3f;
int n, m;
int w[maxn][maxn];
int lx[maxn], ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn], t[maxn];
int chose[maxn];
bool match(int i){
s[i] = 1;
for (int j = 0; j <= m; j++){
int cnt = lx[i] + ly[j] - w[i][j];
if (cnt == 0 && !t[j]){
t[j] = 1;
if (!matched[j] || match(matched[j])){
matched[j] = i;
return 1;
}
}
else{
slack[j] = min(slack[j], cnt);
}
}
return 0;
}
void update(){
int a = inf;
for (int i = 1; i <= m; i++){
if (!t[i])a = min(a, slack[i]);
}
for (int i = 1; i <= n; i++){
if (s[i])lx[i] -= a;
}
for (int i = 1; i <= m; i++){
if (t[i])ly[i] += a;
}
}
void km(){
memset(matched, 0, sizeof(matched));
memset(lx, -0x3f, sizeof(lx));
memset(ly, 0, sizeof(ly));
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
lx[i] = max(lx[i], w[i][j]);
}
}
for (int i = 1; i <= n; i++){
memset(slack, 0x3f, sizeof(slack));
while (1){
memset(s, 0, sizeof(s));
memset(t, 0, sizeof(t));
if (match(i))break;
else update();
}
}
}
int v[maxn],h[maxn],p[maxn],a[maxn],b[maxn];
bool win(int i,int j){
int cnta=h[i],cntb=p[j];
int aa=a[i],bb=b[j];
while(cnta>0&&cntb>0){
cntb-=aa;
if(cntb<=0)break;
cnta-=bb;
}
if(cnta<=0)return 0;
else return 1;
}
int main()
{
while(~scanf("%d",&n),n){
m=n;
for(int i=1;i<=n;i++){
scanf("%d",&v[i]);
v[i]*=100;
}
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<=n;j++){
int cntx=h[i]/b[j];
if(h[i]%b[j])cntx++;
if(cntx*a[i]>=p[j]){w[i][j]=v[i];}
else w[i][j]=-v[i];
if(i==j){w[i][j]++;}
}
}
km();
int ans=0;
int num=0;
for(int i=1;i<=n;i++){
int cnt=matched[i];
if(cnt!=i)num++;
ans+=w[cnt][i];
}
num=n-num;
double gg=double(num)/n;
gg*=100;
if(ans>0){printf("%d %.3lf%%\n",ans/100,gg);}
else printf("Oh, I lose my dear seaco!\n");
}
return 0;
}
大部分的km题需要处理的都只是边的建立,真正匹配的算法都不需要改变,但是理解记忆下次能直接实现还是比较好的。