zoukankan      html  css  js  c++  java
  • 【洛谷6622】[省选联考 2020 A/B 卷] 信号传递(子集DP)

    点此看题面

    • (m)个信号站排成一排,相邻两个距离(1)单位长度,在最左边的信号站左边(1)单位长度有个控制塔。
    • 现在有一个长度为(n)的传导路径(S),表示第(i)次要将信号从编号(S_i)的信号站传向编号(S_{i+1})的信号站。
    • 规定从一个信号站向右传每个单位长度只需(1)点代价;向左传需要先传到控制塔,再由控制塔传到目标信号站,且传每个单位需(k)点代价。
    • 你可以任意决定信号站的编号顺序,求最小的总代价。
    • (nle10^5,mle23)

    贡献转化+子集(DP)

    发现(m)这么小,非常容易想到设(f_S)表示从左向右,选择安放了集合(S)中的所有信号站时的最小代价。

    这里主要是考虑把(x)向左传到(y)的代价拆成两部分,即从(x)(y)(y)到原点一个来回。

    其中从(x)(y)的部分和向右传递是一样的(只是代价不同而已),我们可以把这两部分的代价一起表示到边上。

    即,令(g_S)表示一条边左边的点集为(S),右边的点集为(complement S)时,经过这条边的代价总和。也就是说(g_S)就等于(sum_{i=1}^{n-1}[a_iin S,a_{i+1} otin S]+ksum_{i=1}^{n-1}[a_i otin S,a_{i+1}in S])

    具体预处理过程中,我们先求出(p_{u,v})表示传导路径中二元组((u,v))的个数。

    然后只要任意找到一个(ein S),从(g_{Soplus e})转移,枚举剩余所有元素,消去(Soplus e)中的元素和(e)之间的贡献,加上(e)(complement S)中的元素的贡献即可。

    还有一部分是(y)到原点一个来回的代价,发现这部分与(x)的具体位置是没有联系的,可以直接在把(y)加入集合的时候计算贡献。

    具体地,我们预处理出(c_{y,S})表示集合(S)中所有元素出现在(y)下一位置的总次数,也就是说(c_{y,s}=sum_{i=1}^{n-1}[a_i=y,a_{i+1}in S])

    发现(S)中的元素和(y)计算贡献时是独立的,所以可以任意找到一个(ein S),从(c_{y,Soplus e})转移,加上(y)(e)之间的贡献(c_{y,e})即可。

    这里要注意一个问题,就是(m imes2^m)的数组是开不下的,但实际上我们不需要考虑每个元素在它自己之后的情况,也就是说只需要开一个(m imes2^{m-1})的数组即可,刚好卡在内存上界。

    最后的转移就非常简单了,枚举一个不在(S)中的元素(e),得到:

    [f_{Scup e}=min{f_S+g_S+2(Cnt(S)+1) imes k imes c_{e,complement S}} ]

    其中(Cnt(S))表示(S)(1)的个数,而(Cnt(S)+1)就是(e)的位置,也就是它到控制塔的距离。

    代码:(O(m2^m))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define M 23
    #define Gmin(x,y) (x>(y)&&(x=(y)))
    using namespace std;
    int n,m,k,a[N+5],p[M][M],g[1<<M],_c[M][1<<M-1];long long f[1<<M];
    I int& c(CI x,CI y) {return _c[x][(y>>x+1<<x)|(y&((1<<x)-1))];}//从状态中除去第x位,卡内存
    namespace FastIO
    {
    	#define FS 100000
    	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
    	char oc,FI[FS],*FA=FI,*FB=FI;
    	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
    	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    }using namespace FastIO;
    I int Log(RI x) {RI t=0;W(x>>=1) ++t;return t;}I int Cnt(RI x) {RI t=0;W(x) x^=x&-x,++t;return t;}//Log由状态回到编号;Cnt统计1的个数
    int main()
    {
    	RI i,j,x,l;for(read(n,m,k),l=(1<<m)-1,i=1;i<=n;++i) read(a[i]),--a[i];//方便起见把编号都减1
    	for(i=1;i^n;++i) a[i]^a[i+1]&&++p[a[i]][a[i+1]];for(i=2;i<=n;++i) a[i]^a[i-1]&&++c(a[i],1<<a[i-1]);//统计p以及初始的c
    	for(i=0;i^m;++i) for(j=0;j<=(l>>1);++j) _c[i][j]=_c[i][j^(j&-j)]+_c[i][j&-j];//预处理c,任选一个元素删去后加上贡献
    	for(i=1;i^l;++i) for(x=Log(i&-i),g[i]=g[i^(1<<x)],j=0;j^m;++j)//预处理g,任选一个元素
    		x^j&&(i>>j&1?g[i]-=p[j][x]+k*p[x][j]:g[i]+=p[x][j]+k*p[j][x]);//统计把g从S的补集移到S贡献的变化
    	for(i=1;i<=l;++i) f[i]=1e18;for(i=0;i^l;++i)//子集DP
    		for(x=Cnt(i)+1,j=0;j^m;++j) !(i>>j&1)&&Gmin(f[i^(1<<j)],f[i]+g[i]+2LL*x*k*c(j,l^i));//枚举一个不在集合中的元素转移
    	return printf("%lld
    ",f[l]),0;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    oracle 当行函数 日期
    veridata实验举例(1)验证TCUSTMER与TCUSTORD两节点同步情况
    sdut1730 数字三角形问题(dp入门题)
    Android4.0 Design之UI设计易犯的错误2
    怎样提高团队管理能力6
    Effective C++ 29-33
    内存补齐序列一:关于内存对齐和填充
    【 D3.js 入门系列 --- 10.1 】 简化 GeoJSON 文件
    Android TrafficStats类的使用
    新手上路:Laravel-控制器基础
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu6622.html
Copyright © 2011-2022 走看看