洛谷 2220 [HAOI2012]容易题 题解(组合数学,离散化)

博客介绍了洛谷2220题目的解题思路,该题属于组合数学和离散化的应用。题目要求找到在特定限制下的数列a的所有元素之积之和,其中数列长度m≤109,限制数量k≤105。博主首先分析了无限制时的积的和公式,并探讨了如何处理限制条件对答案的影响。最后,博主提出了一种优化方法,利用k较小的特点,通过快速幂处理未被限制的项,从而解决大数问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原链接:
洛谷:点我QωQ
bzoj:点我QωQ

题意简述

一个长度为 m m m的数列 a a a,每个数都在 [ 1 , n ] [1,n] [1,n]之间,有 k k k个限制。第 i i i个限制包含两个正整数 x , y x,y x,y,表示 a x a_x ax不能取 y y y。求所有满足条件的 a a a数列的所有元素之积之和。

数据

输入

第一行三个正整数, n , m , k n,m,k n,m,k ( n , m &lt; = 1 0 9 , k &lt; = 1 0 5 ) (n,m&lt;=10^9,k&lt;=10^5) (n,m<=109,k<=105)
接下来 k k k行,每行两个正整数 x , y x,y x,y 1 &lt; = x &lt; = m , 1 &lt; = y &lt; = n 1&lt;=x&lt;=m,1&lt;=y&lt;=n 1<=x<=m,1<=y<=n

输出

所有满足条件的 a a a数列的所有元素之积之和。

样例

输入
3 4 5
1 1
1 1
2 2
2 3
4 3
输出
90

解释

有一下几种:

数列
2 1 1 12
2 1 1 24
2 1 2 14
2 1 2 28
2 1 3 16
2 1 3 212
3 1 1 13
3 1 1 26
3 1 2 16
3 1 2 212
3 1 3 19
3 1 3 218

很明显,表中"积"一项的和为 2 + 4 + 4 + 8 + 6 + 12 + 3 + 6 + 6 + 12 + 9 + 18 = 90 2+4+4+8+6+12+3+6+6+12+9+18=90 2+4+4+8+6+12+3+6+6+12+9+18=90

思路

题目名称叫"容易题",但是我非常想叫它"毒瘤题",我艹太 t m ^{tm} tm毒瘤了。。。

虽然样例解释给出的是暴力形式,但是显然我们是不珂以暴力做的。
我们想想(拿 n = 4 , m = 4 n=4,m=4 n=4,m=4举例),如果没有任何限制,那么积的和是多少呢?

显然,每个位置都珂以取 1 , 2 ⋯ n 1,2\cdots n 1,2n,合法的数列有
{ 1 , 2 , 3 , 1 } \{1,2,3,1\} {1,2,3,1}, { 1 , 2 , 3 , 2 } \{1,2,3,2\} {1,2,3,2}, { 1 , 2 , 3 , 3 } \{1,2,3,3\} {1,2,3,3}, { 1 , 2 , 3 , 4 } \{1,2,3,4\} {1,2,3,4}, ⋯ \cdots
这是前四项。如果我们计算它们积的和,我们会发现珂以提一个公因式 1 × 2 × 3 1\times 2\times 3 1×2×3出来,然后最后一项是 ( 1 + 2 + 3 + 4 ) (1+2+3+4) (1+2+3+4),即这四个数列积的和为 ( 1 × 2 × 3 ) × ( 1 + 2 + 3 + 4 ) (1\times 2\times 3)\times (1+2+3+4) (1×2×3)×(1+2+3+4).当然,我们珂以想象一下,如果我们把所有项都因式分解,应该长这样:
= ( 1 + 2 + 3 + 4 ) × ( 1 + 2 + 3 + 4 ) × ( 1 + 2 + 3 + 4 ) × ( 1 + 2 + 3 + 4 ) = ( 1 + 2 + 3 + 4 ) m = ( n ( n + 1 ) 2 ) m =(1+2+3+4)\times (1+2+3+4)\times (1+2+3+4) \times(1+2+3+4)=(1+2+3+4)^m=(\frac{n(n+1)}{2})^m =(1+2+3+4)×(1+2+3+4)×(1+2+3+4)×(1+2+3+4)=(1+2+3+4)m=(2n(n+1))m

此时,我们考虑限制,按样例,第一个不能取 1 1 1。此时我们发现,分解完的结果长成这样:
( 2 + 3 + 4 ) × ( 1 + 2 + 3 + 4 ) 3 (2+3+4)\times(1+2+3+4)^3 (2+3+4)×(1+2+3+4)3
就是第一个括号里少加了一个 1 1 1。也就是说,对于一个限制 ( x , y ) (x,y) (x,y),即第 x x x个位置不能取 y y y,对答案的影响就是第 x x x个括号里减去一个 y y y。(从样例中发现,有些限制会重复出现,这很好办,排序一下,然后 w h i l e while while循环替代 + + i ++i ++i即可)我们用 c n t [ i ] cnt[i] cnt[i]表示第 i i i个括号里的值,每次减一下,到最后乘起来就是答案了。

当你开开心心的写完这个代码的时候,你会发现: w d n m d , m &lt; = 1 0 9 wdnmd,m&lt;=10^9 wdnmd,m<=109!!! F ∗ ∗ K F**K FK
所以我们需要优化。发现 k k k并不大,只有 1 0 5 10^5 105,所以只有这 k k k个做过改动的对答案有影响,其它的就直接快速幂上去即可。由于我们刚刚在去重的时候就已经排过序了,所以代码也就没有什么变动,具体看注释。代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define int long long
    #define mod 1000000007
    #define K 100100
    int n,m,k;
    struct node//保存一个约束
    {
        int pos,val;
        //位置,不能等于的值
    }a[K];bool operator<(node x,node y){return x.pos<y.pos or (x.pos==y.pos and x.val<y.val);}
    //排序:位置第一优先,不能等于的值第二优先
    void Input()
    {
        scanf("%lld%lld%lld",&n,&m,&k);
        for(int i=1;i<=k;++i)
        {
            scanf("%lld%lld",&a[i].pos,&a[i].val);
        }
        sort(a+1,a+k+1);//记得排序
    }

    int cnt[K];//每个括号里的值
    //这里只记录被改动过的括号,别的括号里的值用快速幂乘上去
    int qpow(int a,int b,int m)//快速幂
    {
        int r=1;
        while(b)
        {
            if (b&1) r=r*a%m;
            a=a*a%m,b>>=1;
        }
        return r;
    }
    int Next(int pos)//去重用的,用一个while循环找到第一个不相同的约束条件
    //样例里就有一个相同的...真tm毒瘤..
    {
        ++pos;
        if (a[pos].pos!=a[pos-1].pos) return pos;
        while(a[pos].val==a[pos-1].val) ++pos;
        return pos;
    }
    void Solve()
    {
        int sum=n*(n+1)/2;sum%=mod;//没有被改动过的括号的值
        for(int i=1;i<=k;++i)
        {
            cnt[i]=sum;
        }//初始都是=sum的

        int pos=0;//由于有一些不同的约束条件描述的是一个相同的位置(不能等于的值不同),
        //所以剩下的括号并不是n-k个,而是用这个pos存一下不同的个数
        for(int i=1;i<=k;i=Next(i))
        {
            if (a[i].pos!=a[i-1].pos)
            {
                ++pos;//如果描述的位置不同,就++
            }
            cnt[pos]=(cnt[pos]-a[i].val+mod)%mod;//对答案产生影响,所以要修改括号中的值
            //不能直接模,要+mod再模
        }
        int rest=m-pos;//剩下m-pos个括号里都是sum
        int ans=qpow(sum,rest,mod);//用快速幂乘上去
        for(int i=1;i<=pos;++i)
        {
            ans*=cnt[i];ans%=mod;
        }//这一步上面解释过了
        printf("%lld\n",ans);
    }
    void Main()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        Input();
        Solve();
    }
    #undef int //long long
    #undef mod //1000000007
};
main()
{
    Flandle_Scarlet::Main();
    return 0;
}

回到总题解界面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值