zoukankan      html  css  js  c++  java
  • [HAOI2018]奇怪的背包

    XXXIII.[HAOI2018]奇怪的背包

    神题。

    1. 对于某个大小为\(v\)的物品,它所能表示出的位置的集合等于\(\gcd(v,P)\)所能表示的集合。

    2. 对于某些大小为\(v_1,\dots,v_k\)的物品,位置集合为\(\gcd\{v_1,\dots,v_k,P\}\)

    因此考虑DP。

    我们找出所有\(P\)的约数,存入vector。(这个个数的级别设为\(L\),则\(L\)最大只到\(768\)。)设\(P\)的第\(i\)个约数为\(p_i\)

    则对于所有的\(v_i\),我们找出\(\gcd(P,v_i)\)。设新的\(v_i=\gcd(P,v_i)\)

    对于每个\(p_i\),统计它在\(v_1,\dots,v_n\)中出现了多少次,设为\(s_i\)

    我们设\(f[i][j]\)表示:在\(P\)\(i\)个约数中,选择一些数,使得他们的\(\gcd\)等于\(P\)的第\(j\)个约数的方案数。

    则有

    \(f[i][j]=f[i-1][j]+\Bigg(\small{\begin{cases}1(i=j)\\0(i\neq j)\end{cases}}+\sum\limits_{\gcd(p_k,p_i)=p_j}f[i-1][k]\Bigg)*(2^{s_i}-1)\)

    释义:

    首先,答案是可以从前一位继承来的。

    然后,因为对于每个\(i\),选任何数量的\(v_k=p_i\)\(k\)都是等价的,因此共有\(2^{s_i}-1\)中选法;

    \(i=j\)时,可以之前一个数也不选,就选\(i\)一个数,因此要加上\(1\)

    然后,因为\(\gcd\)具有结合律和交换律,所有\(\gcd(p_k,p_i)=p_j\)的状态也是可继承的。

    \(f[n][j]\)的状态是最终状态。

    对于每个\(w_i\),答案为\(\sum\limits_{v_j|w_i}f[n][j]\)。这个可以通过一个\(L^2\)的预处理求出\(g[i]=\sum\limits_{v_j|v_i}f[n][j]\)算出。

    复杂度\(O(\sqrt{P}+L^2\log L+q)\)

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int mod=1e9+7;
    int n,m,p,s[1001000],f[2][1001000],g[1001000],two[1001000];
    vector<int>v; 
    int main(){
    	scanf("%d%d%d",&n,&m,&p);
    	two[0]=1;
    	for(int i=1;i<=n;i++)two[i]=(two[i-1]<<1)%mod;
    	for(int i=0;i<=n;i++)two[i]=(two[i]-1+mod)%mod;
    //	for(int i=0;i<=n;i++)printf("%d ",two[i]);puts("");
    	for(int i=1;i*i<=p;i++){
    		if(p%i)continue;
    		v.push_back(i);
    		if(i*i!=p)v.push_back(p/i);
    	}
    	sort(v.begin(),v.end());
    //	for(auto i:v)printf("%d ",i);puts("");
    	for(int i=1,x;i<=n;i++)scanf("%d",&x),x=__gcd(x,p),s[lower_bound(v.begin(),v.end(),x)-v.begin()]++;
    //	for(int i=0;i<v.size();i++)printf("%d ",s[i]);puts("");puts("");
    	for(int i=0;i<v.size();i++){
    		for(int j=0;j<=i;j++)f[i&1][j]=0;
    		f[i&1][i]=1;
    		for(int j=0;j<i;j++){
    			int gcd=__gcd(v[i],v[j]);
    			gcd=lower_bound(v.begin(),v.end(),gcd)-v.begin();
    			(f[i&1][gcd]+=f[!(i&1)][j])%=mod;
    		}
    		for(int j=0;j<=i;j++)f[i&1][j]=(1ll*f[i&1][j]*two[s[i]]+f[!(i&1)][j])%mod;
    //		for(int j=0;j<=i;j++)printf("%d ",f[i&1][j]);puts("");
    	}
    	for(int i=0;i<v.size();i++)for(int j=0;j<=i;j++)if(!(v[i]%v[j]))(g[i]+=f[n&1][j])%=mod;
    	for(int i=1,w;i<=m;i++)scanf("%d",&w),w=__gcd(w,p),printf("%d\n",g[lower_bound(v.begin(),v.end(),w)-v.begin()]);
    	return 0;
    } 
    

  • 相关阅读:
    9.3 寻找magic index
    编写一个函数,确定需要改变几个位,才能将整数A转成整数B。
    打印0-1之间double数字的二进制表示
    打印二叉树节点数值总和等于某个给定节点的所有路径
    判断T2是否是T1的子树
    二棵树某两个节点的公共祖先。
    4.6 找出二叉树中指定节点的下一个节点(中序后继),假定每个节点有父指针。
    队列实现max操作,要求尽量提高效率。
    用两个stack设计一个队列
    转分享一个MAC下绕开百度网盘限速下载的方法,三步操作永久生效
  • 原文地址:https://www.cnblogs.com/Troverld/p/14597147.html
Copyright © 2011-2022 走看看