ACM竞赛中的逆向思维

在竞赛过程中,尤其是近期训练,遇到了不少一定要用逆向思维才能解决的题目。
为此做一系列的总结。希望能够对大家有所帮助。
同时,我也会做成PPT,供14级训练使用。

其中有部分问题摘自于2005年国家集训队唐文斌的《正难则反–浅谈逆向思维在解题中的应用》论文。

容斥方面

逆向思维在容斥方面的应用相当广泛,也可以说容斥就是逆向思维的一种体现。

HDU 5072 Coprime 同色三角形

题目大意:

给了 n 个不同的数,要求有多少个三元组,两两互质 或者 两两不互质

思路:

原形是同色三角形问题。
总的三角形的个数是C(n,3),只需减去不同色的三角形即可。这就是逆向思维。
对于每个点(数),与它互质的连红边,不互质的连蓝边,那么对于该点不同色三角形个数为 2 。除以 2 的原因是,对于同一个三角形,我们枚举点的时候被计算了两次。
那么同色三角形个数为 C3n2

问题就变成了:
如何求 原来序列里面的n个数跟某个数k不互质的个数(互质的就是 nk 了)?

可以将原来的 n 个数,每一个都把他们的不同的质因数都求出来,然后枚举它们能够组合的数 1<<cnt ,用一个数组 num 记录,每枚举到一个数,那么数组对应的就 +1

对于数 k ,也把它的不同质因数求出来,同样枚举它能够组合的所有数 t ,然后奇加偶减 num

#include<bits/stdc++.h>
typedef long long ll;
const int N = 200005;

int p[N][15], vis[N], a[N], num[N];
int n;
void Prime()
{
    memset(vis, 0, sizeof vis);
    for(int i = 0; i < N; ++i) p[i][0] = 0;
    for(int i = 2; i < N; ++i) if(!vis[i])
        {
            p[i][ ++p[i][0] ] = i;
            for(int j = i + i; j < N; j += i)
            {
                vis[j] = 1;
                p[j][ ++p[j][0] ] = i;
            }
        }
    p[0][ ++p[0][0] ] = 1;    //考虑0的情况
}

void init()
{
    memset(num, 0, sizeof num);
    for(int k = 0; k < n; ++k)
    {
        int now = a[k];
        int cnt = p[ now ][0];
        for(int i = 1; i < (1 << cnt); ++i)
        {
            int t = 1;
            for(int j = 0; j < cnt; ++j) if((1 << j) & i)
                {
                    t *= p[ now ][j + 1];
                }
            num[t]++;
        }
    }
}

void solve()
{
    ll ans = 0, res, sum = 0;
    ans = (ll)n * (n - 1) * (n - 2) / 6;
    int tot = 0;
    for(int k = 0; k < n; ++k)
    {
        int now = a[k];
        int cnt = p[now][0];
        res = 0;
        for(int i = 1; i < (1 << cnt); ++i)
        {
            int t = 1, g = 0;
            for(int j = 0; j < cnt; ++j) if((1 << j) & i)
                {
                    t *= p[ now ][j + 1];
                    g++;
                }
            if(g & 1)  res += num[t];
            else       res -= num[t];
        }

        if(res == 0) continue;
        sum += (res - 1) * (n - res);
    }
    printf("%lld\n", ans  - sum / 2);

}
int main()
{
    int T;
    scanf("%d", &T);
    Prime();
    while(T --)
    {
        scanf("%d", &n);
        for(int i = 0; i < n; ++i) scanf("%d", &a[i]);
        init();
        solve();
    }
}

ZOJ 1442 Dinner is Ready 不等式解集

此问题的思路来源于唐文斌论文。

题目大意:

妈妈烧了 M 根骨头分给 n 个孩子们,第 i 个孩子有两个参数 Mini Maxi ,分别表示这个孩子至少要得到 Mini 根骨头,至多得到 Maxi 根骨头。
输出一个整数,表示妈妈有多少种分配方案(骨头不能浪费,必须都分给孩子们)。

思路:

这题的模型确实很简单,即求如下方程组的整数解个数。

i=1nXi=MMin1X1Max1Min2X2Max2MinnXnMaxn

我们也知道,方程组简单形式

i=1nXi=MXi0
的整数解个数是 Cn
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值