8/9 基础思维+二分图染色+最大完美匹配KM算法

二分图判定染色法

大佬链接:https://www.bilibili.com/video/BV1sZ4y1i7NZ?spm_id_from=333.999.0.0&vd_source=91973ada1213cf6ba2cbb43a2bebd2e8

定理:二分图不存在奇环,只存在偶数环

#include<bits/stdc++.h>
#define int long long
#define endl '\n'

using namespace std;
const int N=5e5+5;
const int inf=0x3f3f3f3f;
int n,m,cnt,head[N],color[N];
struct node
{
    int to,nxt;
}e[N];
void add(int from,int to)
{
    e[++cnt].to=to;
    e[cnt].nxt=head[from];
    head[from]=cnt;
}
bool dfs(int u,int c) //判断是否是二分图
{
    color[u]=c;
    for(int i=head[u];i;i=e[i].to)
    {
        int v=e[i].to;
        if(!color[v])
            if(dfs(v,3-c)) return 1;
        else if(color[v]==c)
            return 1;
    }
    return 0;
}
signed main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int u,v;cin>>u>>v;
        add(u,v),add(v,u);
    }
    int flag=1;
    for(int i=1;i<=n;i++)
        if(!color[i])
        {
            if(dfs(i,1))
            {
                flag=0;
            }
        }
    if(flag)
        cout<<"YES"<<endl;
    else
        cout<<"NO"<<endl;
    return 0;
}

D. The Number of Imposters

这题属实把我写烦了,代码怎么调都有问题
技巧:
1.主程序放到函数中,会提高编程效率。本题快了将近400ms。
2.N若是e5次方基本,一般定义为700010便可不用管了。
本题思路:
1.若是骗子,说的话一定说明两人是相反阵营。
2.若是正常队员,说的真话,则说明两人一定处于相同阵营。本题学到了一个建立虚点的技巧:采用一个中间点,来进行同类归并

e[u].push_back(g), e[g].push_back(u);
e[g].push_back(v), e[v].push_back(g);

3.要求最大的骗子数,那么对于每一次深搜,两个连通块较大的那个设置为骗子阵营。

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define ios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
using namespace std;
const int N=700010;
const int inf=0x3f3f3f3f;
int n,m,cnt;
int color[N],mx1,mx2;
vector<int>e[N];
bool dfs(int x, int c)
{
	color[x] = c;
	if (x <= n)
    {
		if (c == 1) mx1++;
		else mx2++;
	}
	for (int j : e[x])
	{
		if (!color[j])
		{
			if (!dfs(j, 3 - c))
                return false;
		}
		else if (color[j] == color[x])
            return false;
	}
	return true;
}
void solve()
{
    cin>>n>>m;
    for(int i=0;i<=n+m;i++) e[i].clear(),color[i]=0;
    int id=n;
    for(int i=1;i<=m;i++)
    {
        int u,v;string s;
        cin>>u>>v>>s;
        if(s=="crewmate")
        {
            int g=++id; //建立虚点
            e[u].push_back(g), e[g].push_back(u);
            e[g].push_back(v), e[v].push_back(g);
        }
        else
            e[u].push_back(v), e[v].push_back(u);
    }
    int flag=1;
    int ans=0;
    for(int i=1;i<=id;i++)
    {
        if(!color[i])
        {
            mx1=mx2=0;
            if(!dfs(i,1))
            {
                cout<<-1<<endl;
                return;
            }
            ans+=max(mx1,mx2);
        }
    }
    cout<<ans<<endl;
}
signed main()
{
    ios;
    int t;cin>>t;
    while(t--)
    {
        solve();
    }
    return 0;
}

D. The Door Problem

思路:
1.对于一扇门来说,如果本来就是开的,那么控制改灯的两个开关应属于不用阵营;
2.若一扇门本来是关的,那么控制改灯的两个开关应属于统一阵营

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define ios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
using namespace std;
const int N=700010;
const int inf=0x3f3f3f3f;
int n,m,a[N];
int color[N],mx1,mx2;
vector<int>q[N];
struct node
{
    int to,nxt,w;
}e[N];
int cnt,head[N];
void add(int from,int to,int w)
{
    e[++cnt].to=to;
    e[cnt].w=w;
    e[cnt].nxt=head[from];
    head[from]=cnt;
}
bool dfs(int x, int c)
{
    if(color[x]) 
        return color[x]==c;
	color[x] = c;
	for (int i=head[x];i;i=e[i].nxt)
	{
	    int j=e[i].to;
        if(!dfs(j, e[i].w?c:(3-c)))
            return 0;
	}
	return 1;
}
void solve()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=1;i<=m;i++)
    {
        int sz;cin>>sz;
        for(int j=1;j<=sz;j++)
        {
            int g;cin>>g;
            if(q[g].size()>0)
            {
                add(i,q[g][0],a[g]);
                add(q[g][0],i,a[g]);
            }
            else
                q[g].push_back(i);
        }
    }
    for(int i=1;i<=m;i++)
    {
        if(!color[i])
        {
            if(!dfs(i,1))
            {
                cout<<"NO"<<endl;return;
            }
        }
    }
    cout<<"YES"<<endl;
}
signed main()
{
    ios;
    solve();
    return 0;
}

最大完美匹配KM算法

https://www.bilibili.com/video/BV1cT411J7W5?spm_id_from=333.999.0.0&vd_source=91973ada1213cf6ba2cbb43a2bebd2e8
注释在代码中,详细则要需要观看大佬视频

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define ios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
using namespace std;
const int N=5e3+5;
const int inf=0x3f3f3f3f;
int match[N];       //右部点匹配了哪个左部点
int va[N],vb[N];    //标记是否在交替路中
int la[N],lb[N];    //左顶标、右顶标的值
int w[N][N],d[N];   //维护更新的delta值
int n;
bool dfs(int x)
{
    va[x]=1;
    for(int y=1;y<=n;y++)
    {
        if(!vb[y])
        {
            if(la[x]+lb[y]-w[x][y]==0)
            {
                vb[y]=1;
                if(!match[y]||dfs(match[y]))
                {
                    match[y]=x;return 1;
                }
            }
        }
        else //不是相等子图则记录i最小的d[y]
            d[y]=min(d[y],la[x]+lb[y]-w[x][y]);
    }
    return 0;
}
int KM()
{
    //预处理
    for(int i=1;i<=n;i++) la[i]=-inf;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        la[i]=max(la[i],w[i][j]);
    for(int i=1;i<=n;i++)
        lb[i]=0;
    for(int i=1;i<=n;i++)
    {
        while(1)
        {
            fill(va+1,va+n+1,0);
            fill(vb+1,vb+n+1,0);
            fill(d+1,d+n+1,inf);
            if(dfs(i)) break;
            int delta=inf;
            for(int j=1;j<=n;j++)
                if(!vb[j]) delta=min(delta,d[j]);
            for(int j=1;j<=n;j++)
            {
                if(va[j]) la[j]-=delta;
                if(vb[j]) lb[j]-=delta;
            }
        }
    }
}
void solve()
{
    memset(w,-inf,sizeof w);

}
signed main()
{
    ios;
    int t;cin>>t;
    while(t--)
    {
        solve();
    }
    return 0;
}

P2457 [SDOI2006]仓库管理员的烦恼

思路:
1.模板求的是最大完美匹配,而本题需要使用二分图KM算法,却求的是最小代价。
2.需要变型。左部点为货物,右部点为仓库。将第i种货物搬到第j个仓库所需代价,注意要减去该仓库本身就有的该类货物。
3.最小代码,将权值取反,跑最大完全匹配。再将最后结果取反。

代码:

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define For(i,a,b) for(i=(a);i<=(b);++i)
#define ios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
using namespace std;
const int N=5e2+5;
const int inf=0x3f3f3f3f;
int match[N];       //右部点匹配了哪个左部点
int va[N],vb[N];    //标记是否在交替路中
int la[N],lb[N];    //左顶标、右顶标的值
int w[N][N],d[N];   //维护更新的delta值
int n,sum[N],mp[N][N];
bool dfs(int x)
{
    va[x]=1;
    for(int y=1;y<=n;y++)
    {
        if(!vb[y])
        {
            if(la[x]+lb[y]-w[x][y]==0)
            {
                vb[y]=1;
                if(!match[y]||dfs(match[y]))
                {
                    match[y]=x;return 1;
                }
            }
            else //不是相等子图则记录i最小的d[y]
                d[y]=min(d[y],la[x]+lb[y]-w[x][y]);
        }
    }
    return 0;
}
int KM()
{
    //预处理
    for(int i=1;i<=n;i++) la[i]=-inf;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        la[i]=max(la[i],w[i][j]);
    for(int i=1;i<=n;i++)
        lb[i]=0;
    for(int i=1;i<=n;i++)
    {
        while(1)
        {
            fill(va+1,va+n+1,0);
            fill(vb+1,vb+n+1,0);
            fill(d+1,d+n+1,inf);
            if(dfs(i)) break;
            int delta=inf;
            for(int j=1;j<=n;j++)
                if(!vb[j]) delta=min(delta,d[j]);
            for(int j=1;j<=n;j++)
            {
                if(va[j]) la[j]-=delta;
                if(vb[j]) lb[j]+=delta;
            }
        }
    }
    int res=0;
    for(int i=1;i<=n;i++)
        res+=w[match[i]][i];
    return res;
}

void solve()
{
    memset(w,-inf,sizeof w);
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
    {
        int x;cin>>x;
        mp[i][j]=x; //仓库---货物
        sum[j]+=x;  //每种货物总数
    }
    for(int i=1;i<=n;i++) //该种货物搬到第j个仓库所需代价
    {
        for(int j=1;j<=n;j++)
            w[i][j]=-(sum[i]-mp[j][i]);
    }
    cout<<-KM()<<endl;
}
signed main()
{
    //ios;
    solve();
    return 0;
}


B. Party

这个B题属实有点卡我了,想不到思路,有点巧妙
思路:
1.若m为偶数,则所有成对出现的队员都可参与,怨气值为0.
2.若m为奇数,面临两种选择,一种是一对成员不可参加;一种是单个成员且出现次数为奇数队员不可参加。出现奇数次的队员不去,则m-奇数为偶数,符合题意。

#include<bits/stdc++.h>
#define int long long
#define endl '\n'

using namespace std;
const int N=5e5+5;
const int inf=0x3f3f3f3f;
int n,m,a[N],d[N];

signed main()
{
    int t;cin>>t;
    while(t--)
    {
        cin>>n>>m;
        for(int i=1;i<=n;i++)
            cin>>a[i],d[i]=0;
        int s1=inf,s2=inf;
        for(int i=1;i<=m;i++)
        {
            int x,y;cin>>x>>y;
            d[x]++,d[y]++;
            s1=min(a[x]+a[y],s1);
        }
        if(m%2)
        {
            for(int i=1;i<=n;i++)
                if(d[i]&1)
                s2=min(s2,a[i]);
            cout<<min(s1,s2)<<endl;
        }
        else
            cout<<0<<endl;
    }
    return 0;
}

A. Perfect Permutation

记住这个排列,很有用: n 1 2 3 4 …… n-1

#include <bits/stdc++.h>
#define int long long
#define endl '\n'

using namespace std;
const int N =2e5+100;
const int mod=998244353;
const int inf=0x3f3f3f3f;
int n;

signed main()
{
    int t;cin>>t;
    while(t--)
    {
        cin>>n;
        if(n==1)
        {
            cout<<1<<endl;continue;
        }
        cout<<n<<" ";
        for(int i=1;i<n;i++)
            cout<<i<<" ";
        cout<<endl;
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值