首页 > 其他分享 > P3477 [POI2008] PER-Permutation 解题报告

P3477 [POI2008] PER-Permutation 解题报告

时间:2023-10-02 09:03:31浏览次数:47  
标签:cnt POI2008 int PER 模数 P3477 maxn multiset Mod

我咕咕咕了这道题半年之久?

好像洛谷好多题解都被 hack 了啊,但是没有被撤。

(本题解现有 hack 均通过)

题目链接

折叠题干

[POI2008] PER-Permutation

题目描述

Multiset is a mathematical object similar to a set, but each member of a multiset may have more than one membership.

Just as with any set, the members of a multiset can be ordered in many ways. We call each such ordering a permutation of the multiset. For example, among the permutations of the multiset{1,1,2,3,3,3,7,8}. there are {2,3,1,3,3,7,1,8} and{8,7,3,3,3,2,1,1}.

We will say that one permutation of a given multiset is smaller (in lexicographic order) than another permutation, if on the first position that does not match the first permutation has a smaller element than the other one. All permutations of a given multiset can be numbered (starting from one) in an increasing order.

Task Write a programme that reads the description of a permutation of a multiset and a positive integerm from the standard input, determines the remainder of the rank of that permutation in the lexicographic ordering modulo m, writes out the result to the standard output.

多重集合是数学中的一个概念,它的定义很像集合,但是在多重集之中,同一个元素可以出现多次。

和集合一样,多重集的的元素可以有很多种元素的排布顺序。我们把它叫作多重集的排列。

现在我们定义多重集的某个排列\(s_i\)比某个排列\(s_j\)

的大小比较为字典序比较。这样某个多重集的排列可以从小到大得排起来。

现在给你一个元素个数为n的多重集的一个排列和m,求这个排列的排名取模m。

输入格式

The first line of the standard input holds two integers n(\(1\le n \le 300000\)) and m (\(2 \le m \le 10^9\) ) ,separated by a single space. These denote, respectively, the cardinality of the multiset and \dots\ the number m.

The second line of the standard input contains n positive integers \(a_i\)(\(1\le a_i \le 300000\)), separated by single spaces and denoting successive elements of the multiset permutation.

第一行 两个整数n,m

第二行 n个数,代表多重集的排列

输出格式

The first and only line of the standard output is to hold one integer, the remainder modulo m of the rank of the input permutation in the lexicographic ordering.

一行一个整数 排名取模m

样例 #1

样例输入 #1

4 1000
2 1 10 2

样例输出 #1

5

提示

感谢@远航之曲 贡献的翻译

解题思路:

首先看到这道题求排名,感觉还挺像康托展开的,但是康拓展开求的是 不会出现重复元素的排列 的排名,而这道题的难题主要有两个:

可重复和未知模数。

对于可能重复的元素:

我们思考康托展开本身的公式。

对于一个 \(1\) 到 \(n\) 的排列:\({a_1,a_2,a_3,\dots,a_n}\),其排名为:

\[\sum_{i=1}^{n} sum_{a_i}\cdot (n-i)! \]

而 \(sum_{a_i}\) 表示在 \(a_i\) 之后比它元素小的个数。

int ans=0;
for(int i=1;i<=n;i++){
    int sum=0;
   	for(int j=i;j<=n;j++)
	if(a[i]<a[j])sum++;//统计sum
    ans=(ans+sum*jc[n-i])%998244353;//计算ans                       
}
printf("%d",ans+1);//别忘了+1

那考虑如何去重呢?

我们的 \((n-i)!\) 表示的是 \((i+1)-n\) 位置所有的方案数,对于不重复元素,两个元素互换位置是不同的方案,而重复的元素,两个元素互换位置方案相同,造成了方案数的重复计算。

因此,我们考虑将重复元素提出来,例如 \({39,39,39,39}\),它是一个方案,却计了 \(4!\) 次。

所以,设从 \(i-n\) 位置一共有 \(piece_i\) 种元素,\(cnt_{j}\) 表示在当前的 \(piece_i\) 下的第 \(j\) 个元素,答案为:

\[ans=\sum_{i=1}^{n} \dfrac{sum_{a_i}\cdot (n-i)!}{\prod_{j=1}^{piece_{i}} cnt_j} \]

然后你发现,如果从左往右扫,不断删除元素对于 \(cnt_j\) 的处理较难,所以考虑从右往左倒序枚举,这样就成为了加入元素,无论是分子还是分母都较为简单。

对于不明的模数

对于不定模数,或许有两种解决方式:

  • 使用不依赖模数性质的算法或利用给出的模数自带的性质。

  • 对模数进行特殊的处理。

不知道数据有没有 114514 一类的模数,但是显然 114514 不是质数,所以费马小定理不能使用。

而我们只能考虑扩展欧几里得算法了,它要求我们求解的模数与求解数互质。

所以我们可以对分母 \(\prod_{j=1}^{piece_{i}} cnt_j\) 和模数分解质因数,对于模数没有的质因子,扩展欧几里得求逆元。

对于共同的质因子,我们考虑直接消去,因为 \(ans\) 是整数,所以 \(\dfrac{sum_{a_i}\cdot (n-i)!}{\prod_{j=1}^{piece_{i}}\limits cnt_j} cnt_j\) 是整数,所以若存在共同质因子 \(p_i\),其分子一定存在比分母更多的质因子 \(p_i\),所以直接消去分子和分母所有的 \(p_i\),然后使分子乘上本身比分母多的 \(p_i\) 即可。

(tips:有关Hack,请注意这里的分子包含 \(suma\),即帖中的 \(w\)。)

使用树状数组优化一下,跑的还是比较快的。

Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define cout std::cout
#define cerr std::cerr
#define endl '\n'
#define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef double ff;
typedef long double llf;
const ff eps=1e-8;
int Max(int x,int y)    <% return x<y?y:x; %>
int Min(int x,int y)    <% return x<y?x:y; %>
int Abs(int x)  <% return x>0?x:-x; %>
#if ONLINE_JUDGE
char INPUT[1<<20],*p1=INPUT,*p2=INPUT;
#define getchar() (p1==p2 && (p2=(p1=INPUT)+fread(INPUT,1,1<<20,stdin),p1==p2)?EOF:*p1++)
#endif
il int read(){
    char c=getchar();
    int x=0,f=1;
    while(c<48) <% if(c=='-')f=-1;c=getchar(); %>
    while(c>47) x=(x*10)+(c^48),c=getchar();
    return x*f;
}const int maxn=3e5+5;

int n,Mod,ptot,p[maxn],a[maxn],sa[maxn],rank[maxn],len;
int cnt_numerator[maxn],cnt_denominator[maxn];
ll sum[maxn],ans,cnt[maxn];

il ll mymod(ll x){ return x<Mod?x:x-Mod; }

il ll qpow(ll x,int k){
// x^k
    ll res=1;
    while(k){
        if(k&1) res=res*x%Mod;
        x=x*x%Mod;
        k=k>>1;
    }
    return res;
}

il void exgcd(int a,int b,int &x,int &y){
// 求得x是a在模Mod意义下的乘法逆元
    if(!b)  return x=1,y=0,void();
    exgcd(b,a%b,y,x);y=(y-(a/b)*x%Mod+Mod)%Mod;
}

#define lowbit(x) (x&-x)
il ll query(int pos){
    ll res=0;
    while(pos){
        res+=sum[pos];
        pos-=lowbit(pos);
    }
    return res;
}
il void update(int pos,int val){
    while(pos<=len){
        sum[pos]+=val;
        pos+=lowbit(pos);
    }
}
#undef lowbit

il ll solve_numerator(ll numerator){
// 对分子分解质因数
    if(!numerator)  return 1;
    for(rg i=1;i<=ptot;++i){
        if(numerator%p[i])  continue;
        while(!(numerator%p[i]))  ++cnt_numerator[i],numerator/=p[i];
    }
    return numerator;
}

il void back(ll numerator){
// 撤回ssum对cnt_numerator的影响
    if(!numerator)  return;
    for(rg i=1;i<=ptot;++i){
        if(numerator%p[i])  continue;
        while(!(numerator%p[i]))  --cnt_numerator[i],numerator/=p[i];
    }
}

il ll solve_denominator(ll denominator){
// 对分母分解质因数
    if(!denominator)    return 1;
    for(rg i=1;i<=ptot;++i){
        if(denominator%p[i])    continue;
        while(!(denominator%p[i]))    ++cnt_denominator[i],denominator/=p[i];
    }
    return denominator;
}

il void work(){
    int numerator=1,denominator=1,res=1,x=0,y=0,save;
    for(rg i=n;i>=1;--i){
        res=1;
        ++cnt[a[i]];
        numerator=numerator*solve_numerator(n-i)%Mod;
        save=numerator;
        denominator=denominator*solve_denominator(cnt[a[i]])%Mod;
        update(rank[i],1);
        int ssum=query(rank[i]-1);
        if(!ssum)   continue;   // 没有比当前位小的直接continue
        numerator=numerator*solve_numerator(ssum)%Mod;
        exgcd(denominator,Mod,x,y);     // 求非共同质因子的逆元x
        x=mymod(x%Mod+Mod);
        for(rg j=1;j<=ptot;++j) res=res*qpow(p[j],cnt_numerator[j]-cnt_denominator[j])%Mod; // 消去共同质因子
        res=res*numerator%Mod*x%Mod;
        ans=mymod(ans+res);
        back(ssum);numerator=save;
    }
}

il void input(){
    n=read(),Mod=read();
    for(rg i=1;i<=n;++i)    a[i]=read(),sa[i]=rank[i]=a[i];
    std::sort(sa+1,sa+1+n);
    len=std::unique(sa+1,sa+1+n)-(sa+1);
    for(rg i=1;i<=n;++i)    rank[i]=std::lower_bound(sa+1,sa+1+len,rank[i])-sa; // 离散化
    int save=Mod;
    for(rg i=2;i<=sqrt(Mod);++i){
        if(Mod%i)   continue;
        p[++ptot]=i;
        while(!(Mod%i))   Mod/=i;
    }
    if(Mod^1)   p[++ptot]=Mod;
    Mod=save;
}

signed main(){
#ifndef ONLINE_JUDGE
freopen("PER.in","r",stdin);
#endif
    input();
    work();
    printf("%lld\n",mymod(ans+1));
    return 0;
}

标签:cnt,POI2008,int,PER,模数,P3477,maxn,multiset,Mod
From: https://www.cnblogs.com/sonnety-v0cali0d-kksk/p/PER.html

相关文章

  • 无涯教程-JavaScript - UPPER函数
    描述UPPER函数将文本转换为大写。语法UPPER(text)争论Argument描述Required/OptionalText您要转换为大写的文本。文本可以是引用或文本字符串。Required适用性Excel2007,Excel2010,Excel2013,Excel2016Example参考链接https://www.learnfk.com/javascri......
  • 为什么在es6中继承必须调用super函数?
    在ES6中规定,子类的构造函数必须要执行super函数图片查阅自阮一峰ES6教程super()函数有什么作用?在执行super函数时,其实就是在创建子类的this,然后将父类的实例和方法放置在这个this对象中,子类在调用super之前是没有this的,所有的this操作都要在super()关键字后执行......
  • Hyper-V 安装 CentOS 8.5
    前言Hyper-V安装文档:在Windows10上安装Hyper-VCentOS系统下载:CentOS国内镜像源8.5.2111作者:易墨发布时间:2023.10.01原文地址:https://www.cnblogs.com/morang/p/devops-hyperv-centos-install.html使用命令安装以管理员身份运行PowerShell命令:Enable-WindowsOpt......
  • TypeError: unsupported operand type(s) for |: 'type' and 'NoneType' [duplicate]
      str|Nonesyntaxisonlysupportedin3.10orlater.UsefromtypingimportOptionalname:Optional[str]=NoneForcaseswheretherighthandsideisn'tNoneortherearemorethantwotypes,youcanuseUnionfromtypingimportUnionfoo:U......
  • 免费 AI 代码生成器 Amazon CodeWhisperer 初体验
    文章作者:浪里行舟简介随着ChatGPT的到来,不由让很多程序员感到恐慌。虽然我们阻止不了AI时代到来,但是我们可以跟随AI的脚步,近期我发现了一个神仙AI代码生产工具CodeWhisperer,它是一项基于机器学习的服务,其根据自然语言注释和集成开发环境(IDE)中的代码,生成代码建议,帮助......
  • new、::operator new与placement new的区别
    在内存管理中,::operatornew()/::operatordelete()、delete/new、placementnew是不同的:::operatornew():只是进行空间的申请而不调用构造函数,可以理解为只是对malloc的简单封装,返回void*。可以进行类内重载或者全局重载,类内没有重载则寻找全局new。::operatordelete()......
  • java springboot项目,mybatisplus,import com.baomidou.mybatisplus.core.mapper.BaseMa
    <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.1.2</version><!--用版本2.1.9就不行,UserMapper里BaseMapper爆红--></dependency>我的结果是,......
  • 使用 AI 编程助手 CodeWhisperer,开发如有神助
    前段时间体验了chatGPT,听说它可以写代码,结果发现更多的只是一个对答写小作文的百度助手,虽然也能写代码,但不是我想要的,可以在idea中可以快速生成代码块的。一个偶然的机会,从微信群里了解到,由亚马逊云科技推出的CodeWishPerer开发插件,可以在多个开发环境中使用,如:VisualStudio(VS......
  • 2023 ICPC 网络赛2 L Super-palindrome 字符串 border KMP dp
    传送门给出一个\(5000\)长的字符串判断有多少个连续子串是超级回文的。这里超级回文的定义是将字符串分成\(2k\)段每段按照回文对应相等。设\(f_{l,r}\)表示区间\(l,r\)是否是符合要求的。引入\(border\)的定义:最长的前缀和后缀匹配长度。容易想到我们如果暴力枚举每个区间来......
  • 秒懂Zookeeper原理与工作机制
    什么是ZookeeperZookeeper简称zk,先从字面意思上去理解,那就是动物园管理员。其实zk是大数据领域中的一员,为整个分布式环境提供了协调服务,主要可以用于存储一些配置信息,同时也可以基于zk实现集群。它是一个apache的开源分布式中间件。zk和大数据领域结合比较密切,可以管理很多框架,比如......