BZOJ1931 : [Shoi2007]Permutation 有序的计数

枚举LCP以及下一位变小成什么,统计出剩下的有几个可以在原位置。

然后枚举剩下的至少有几个在原位置,容斥计算答案。

时间复杂度$O(n^3)$。

#include<cstdio>
typedef long long ll;
const int N=70,B=10000,MAXL=30;
int n,i,j,a[N],b[N],v[N],fun;
inline int max(int a,int b){return a>b?a:b;}
struct Num{
  int a[MAXL],len,fu;
  Num(){len=1,fu=a[1]=0;}
  Num operator+(Num b){
    Num c;
    c.len=max(len,b.len)+2;
    int i;
    for(i=1;i<=c.len;i++)c.a[i]=0;
    if(fu==b.fu){
      for(i=1;i<=len;i++)c.a[i]=a[i];
      for(i=1;i<=b.len;i++)c.a[i]+=b.a[i];
      for(i=1;i<=c.len;i++)if(c.a[i]>=B)c.a[i+1]++,c.a[i]-=B;
      while(c.len>1&&!c.a[c.len])c.len--;
      c.fu=fu;
    }else{
      bool flag=0;
      if(len==b.len){
        for(i=len;i;i--)if(a[i]!=b.a[i]){
          if(a[i]>b.a[i])flag=1;
          break;
        }
      }else{
        if(len>b.len)flag=1;
      }
      if(flag){
        for(i=1;i<=len;i++)c.a[i]=a[i];
        for(i=1;i<=b.len;i++)c.a[i]-=b.a[i];
        for(i=1;i<=c.len;i++)if(c.a[i]<0)c.a[i+1]--,c.a[i]+=B;
        while(c.len>1&&!c.a[c.len])c.len--;
        c.fu=fu;
      }else{
        for(i=1;i<=b.len;i++)c.a[i]=b.a[i];
        for(i=1;i<=len;i++)c.a[i]-=a[i];
        for(i=1;i<=c.len;i++)if(c.a[i]<0)c.a[i+1]--,c.a[i]+=B;
        while(c.len>1&&!c.a[c.len])c.len--;
        c.fu=b.fu;
      }
    }
    return c;
  }
  Num operator-(Num b){
    b.fu^=1;
    return *this+b;
  }
  Num operator*(Num b){
    Num c;
    c.len=len+b.len+2;
    c.fu=fu^b.fu;
    int i,j;
    for(i=1;i<=c.len;i++)c.a[i]=0;
    for(i=1;i<=len;i++)for(j=1;j<=b.len;j++){
      c.a[i+j-1]+=a[i]*b.a[j];
      if(c.a[i+j-1]>=B){
        c.a[i+j]+=c.a[i+j-1]/B;c.a[i+j-1]%=B;
        if(c.a[i+j]>=B)c.a[i+j+1]+=c.a[i+j]/B,c.a[i+j]%=B;
      }
    }
    while(c.len>1&&!c.a[c.len])c.len--;
    return c;
  }
  void write(){
    printf("%d",a[len]);
    for(int i=len-1;i;i--)printf("%04d",a[i]);
  }
  void set(int x){
    fu=0;
    len=1;
    a[1]=x;
  }
}f[N],C[N][N],ans,tmp;
void solve(int x,int y){
  int i,j,now;
  for(i=0;i<x;i++)b[i]=a[i];
  b[x]=y;
  for(i=0;i<n;i++)v[i]=0;
  for(i=0;i<=x;i++){
    if(v[b[i]])return;
    v[b[i]]=1;
  }
  for(now=i=0;i<=x;i++)now+=b[i]==i;
  if(now>fun)return;
  now=fun-now;
  int ret=0;
  for(i=x+1;i<n;i++)if(!v[i])ret++;
  for(i=now;i<=ret;i++){
    Num t=f[n-1-x-i]*C[ret][i]*C[i][now];
    if((i-now)&1)ans=ans-t;else ans=ans+t;
  }
}
int main(){
  scanf("%d",&n);
  for(f[0].set(i=1);i<=n;i++)tmp.set(i),f[i]=f[i-1]*tmp;
  for(C[0][0].set(i=1);i<=n;i++)for(C[i][0].set(j=1);j<=i;j++)C[i][j]=C[i-1][j-1]+C[i-1][j];
  for(i=0;i<n;i++)scanf("%d",&a[i]),fun+=a[i]==i;
  printf("%d ",fun);
  for(i=0;i<n;i++)for(j=0;j<a[i];j++)solve(i,j);
  ans.write();
  return 0;
}

  

时间: 2024-10-13 22:26:52

BZOJ1931 : [Shoi2007]Permutation 有序的计数的相关文章

(ST表+二分+前缀和)CSU 1879 - Hack Protection

题意: 给定一个序列,求异或和与按位与和相同的区间有几个. 异或和:n个数异或起来.按位与和类似. 分析: 这才是神题,基础算法大杂烩. 问大佬这题的时候,人家只说很不难啊.. 只能说自己太菜. 由于询问区间个数,自然要快速知道某一个区间的异或和与按位与和. 异或和很简单,利用他的性质,直接求前缀和即可. 但是按位与没有和加法和异或类似的性质,无法直接求出. 但是利用之前的知识可以将所有结果预处理出来,而且只要nlogn的复杂度. 就是之前搞RMQ的ST表,很适合离线查询. 几乎不用动的把 mi

codeforce Hello 2020 B、C

签到5分钟,挂机2小时 然后掉分 想了两个小时B,想着用树状数组维护数量,但实际上他不动态的去更改的话,似乎用个数组,最多再统计一下前缀和,即可实现树状数组的功能,太假了.后来想的set,pair. 总而言之,没把问题想透. B New Year and Ascent Sequence 其实对一个序列而言,如果他存在一个递增的子序列,那么所有别的序列+这个序列合成产生的答案都是满足的.所以这些序列对答案的贡献n都是满的. 我们只对另一些序列进行操作,对于那些不存在递增子序列的序列,我们给他标记,

HDU 4917 Permutation 拓扑排序的计数

题意: 一个有n个数的排列,给你一些位置上数字的大小关系.求合法的排列有多少种. 思路: 数字的大小关系可以看做是一条有向边,这样以每个位置当点,就可以把整个排列当做一张有向图.而且题目保证有解,所以只一张有向无环图.这样子,我们就可以把排列计数的问题转化为一个图的拓扑排序计数问题. 拓扑排序的做法可以参见ZJU1346 . 因为题目中点的数量比较多,所以无法直接用状压DP. 但是题目中的边数较少,所以不是联通的,而一个连通块的点不超过21个,而且不同连通块之间可以看做相互独立的.所以我们可以对

概率论快速学习01:计数

2014-05-15 22:02 by Jeff Li 前言 系列文章:[传送门] 马上快要期末考试了,为了学点什么.就准备这系列的博客,记录复习的成果. 正文-计数  概率 概率论研究随机事件.它源于赌徒的研究.即使是今天,概率论也常用于赌博.随机事件的结果是否只凭运气呢?高明的赌徒发现了赌博中的规律.尽管我无法预知事件的具体结果,但我可以了解每种结果出现的可能性.这是概率论的核心. "概率"到底是什么?这在数学上还有争议."频率派"认为概率是重复尝试多次,某种结

概率论1 计数-排列-组合

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 概率 概率论研究随机事件.它源于赌徒的研究.赌博中有许多随机事件,比如投掷一个骰子,是否只凭运气呢? 赌徒逐渐发现随机事件的规律.投掷两个骰子是常见的赌博游戏.如果重复很多次,那么总数为2的次数会比总数7的次数少.这就是赌徒把握到的规律:尽管我无法预知事件的具体结果,但我可以了解每种结果出现的可能性.这是概率论的核心. "概率"到底是什么?这在数学上还有争议."

计数排序(Count Sort )与插入排序(Insert Sort)

计数排序法:计数数组适用于当前数组密集的情况.例如(2,3,5,4,2,3,3,2,5,4) 方法:先找出最大值最小值,之后统计每个数出现的次数,根据次数从小到大往数组里添加 计数排序法是一种不需要比较的排序方法 1 void count(int top,int length,int arr[]) 2 { 3 int min=arr[0],max=arr[0],i=1,j=0; 4 int *count=(int*)malloc(sizeof(int)*(max-min+1)); 5 if(ar

【数据结构】非比较排序算法(实现计数排序和基数排序)

● 计数排序 1.算法思想: 计数排序是直接定址法的变形.通过开辟一定大小的空间,统计相同数据出现的次数,然后回写到原序列中. 2.步骤: 1)找到序列中的最大和最小数据,确定开辟的空间大小. 2)开辟空间,利用开辟的空间存放各数据的个数. 3)将排好序的序列回写到原序列中. 具体实现如下: void CountSort(int *arr, int size) {  assert(arr);  int min = arr[0];  int max = arr[0];  int num = 0;

【数组】Next Permutation

题目: Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers. If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order). The repla

三种线性排序算法(计数、基数、桶排序)的简单实现

一.计数排序 计数排序假设n个输入元素中的每一个都是介于0到k之间的整数.此处k为某个整数(输入数据在一个小范围内). 基本思想: 计数排序的基本思想是对每一个输入元素x,确定出小于x的元素的个数.然后再将x直接放置在它在最终输出数组中的位置上. 如下图所示: 由于数组中可能有相等的数,在处理时需要注意. 时间复杂度和空间复杂度分析 算法总时间Θ(k + n).当k=O(n)时,计数排序的运行时间是Θ(n). 空间复杂度是O(n+k).需要两个辅助数组:存放排序结果的数组B[n],存放临时结果的