【CodeVS 1222】信与信封的问题 随机化+treat Hungery

题目

n 封信依次编号为1 n n个信封也依次编号为 1 n.
Small John能提供一组信息:第 i 封信肯定不是装在信封j中.
请编程帮助Small John,确定一定匹配的信和信封.

1n100

分析

这是存在性问题,有两种思路:
①枚举所有的情况,找情况交;
②枚举所有可能的单个匹配,假设某种情况存在,进行验证;

【思路①】treat,AC

为了使效率尽可能高,我们要尽可能找较少的情况.
这较少的情况中要包含所有的有效情况,也就是所有肯定匹配的边都要包含在内.

存在性问题往往跟最值问题有关,不难想到一个最大匹配中必定包含所有的肯定匹配的边,但是最大匹配中有些边可能不是一定匹配的.
而枚举所有的最大匹配应该也会超时.

但是最大匹配中不满足的边应该不会很多.
我们考虑随机扩展顺序,求出 T 个最大匹配,然后求交.

时间复杂度为O(n3T).
现在要考虑 T 的取值.

由于匈牙利算法的实际效率大概是O(n2),所以不妨设 T=10000000100100=1000 ,进行 1000 次随机化.

这个代码虽然是骗分的,但是 T=1000 可以AC.
然而经检验发现,当 T=5 ,都可以AC,而且实测2ms,跑得贼快~

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <ctime>
#include <algorithm>
using namespace std;

const int N=128;
const int T=1000;

int n;
int p[N][N];

inline int read(void)
{
    int x=0; char c=getchar();
    for (;!isdigit(c);c=getchar());
    for (;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x;
}

int ran[N];
int ran2[N];

int fltor[N];

int used[N];
int link[N];
int ltor[N];

int hungery(int now)
{
    for (int i=1;i<=n;i++)
        if (!used[ran2[i]]&&p[now][ran2[i]])
        {
            used[ran2[i]]=1;
            if (!link[ran2[i]]||hungery(link[ran2[i]]))
            {
                link[ran2[i]]=now;
                return 1;
            }
        }
    return 0;
}

int main(void)
{
    int x,y; n=read();
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++) p[i][j]=1;
    for (;;)
    {
        x=read(),y=read();
        if (!x&&!y) break;
        p[x][y]=0;
    }

    srand(time(0));
    for (int i=1;i<=n;i++) ran[i]=i;
    for (int i=1;i<=n;i++) ran2[i]=i;

    for (int times=1;times<=T;times++)
    {
        random_shuffle(ran+1,ran+n+1);
        random_shuffle(ran2+1,ran2+n+1);

        memset(link,0,sizeof link);
        for (int i=1;i<=n;i++)
        {
            memset(used,0,sizeof used);
            hungery(ran[i]);
        }

        for (int i=1;i<=n;i++)
            if (link[i]) ltor[link[i]]=i;

        if (times==1)
            memmove(fltor,ltor,sizeof ltor);
        else
            for (int i=1;i<=n;i++)
                if (~fltor[i]) fltor[i]=fltor[i]==ltor[i]?ltor[i]:-1;
    }

    int flag=0;
    for (int i=1;i<=n;i++)
        if (~fltor[i]) printf("%d %d\n",i,fltor[i]),flag=1;
    if (!flag) printf("none\n");

    return 0;
}

【思路②】hungery,AC

接下来考虑思路②.

根据之前的分析,一个最大匹配中必定包含所有的肯定匹配的边.
我们先枚举最大匹配的每一条边,然后假设它一定需要,试着删除它,看看能不能凑出新的匹配方案.
如果不行,说明这条边是一定匹配的.

时间复杂度为 O(n3) .

代码:不写了~

小结

判定性问题中一定存在性问题的思路:
①求所有的情况交;
②对所有的可能存在的情况,假设它不存在了,验证没有另外的方案.

另外,要存在性问题往往可以与最优性问题扯上关系.

还有就是用枚举法解决的问题,可以考虑多次随机代替.
而枚举法常用于最优性问题,这意味着多次随机可用于最优性问题.

重新整理一下最优性问题的解决策略:dp,贪心,随机化/枚举实现的检索,图论的一些方法,(二分)参数搜索.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值