• 【2020省选Day2T1】 LOJ3302 「联合省选 2020 A | B」信号传递


    题目链接

    初步转化

    按题意,我们暴力枚举这(m)个信号站的排列顺序,时间复杂度(O((m!)cdot n))

    容易发现,题目给出的这个长度为(n)的序列(S)具体是什么不重要,重要的是,每一对信号站(i,j),在(S)里作为相邻的位置,出现了多少次。也就是有多少个位置(p) ((1leq p<n))满足(S_p=i,S_{p+1}=j)。我们记这个数量为( ext{cnt}[i][j])

    对于任意(i eq j)( ext{cnt}[i][j])就表示要从信号站(i),向信号站(j),进行多少次传递。设两个信号站的位置,分别为( ext{pos}[i], ext{pos}[j]),则它们会对答案产生的代价就是:

    [egin{cases} ext{pos}[j]- ext{pos}[i]&&( ext{pos}[i]< ext{pos}[j])\ kcdot( ext{pos}[i]+ ext{pos}[j])&&( ext{pos[i]}> ext{pos}[j]) end{cases} ]

    做了这步转化后,如果还是暴力枚举这(m)个信号站的排列顺序,时间复杂度优化为(O(n+(m!)cdot m^2))。至此我们复杂度的瓶颈与(n)无关了。

    朴素DP

    接下来,结合(m)的大小,容易想到状压DP。此处就有两种状态设计的思路:

    • (dp[s])表示考虑了前(|s|)个数,放在了(s)里的这些位置上。
    • (dp[s])表示考虑了前(|s|)个位置,填了(s)里的这些数。

    发现,第一种状态设计是无法转移的。所以我们选择第二种。事实上,考试时我就一直在想第一种,把自己搞自闭了。其实,按位置考虑、按数值考虑,这都是常见的方法,所以要学会灵活变通,一个不行就想想另一个。

    我们选择了第二种状态设计:设(dp[s])表示考虑了前(|s|)个位置,填了(s)里的这些数。

    那考虑转移。首先要枚举在新加入的位置,也就是在第( ext{pos}=|s|+1)个位置,填哪个数。假设我们填(i)。考虑填(i)会新增哪些代价。它会分别和:前面的数(也就是(s)里的数)、后面的数(也就是不在(s)里,但也不等于(i)的数)产生代价。而且每种代价都有两个方向。具体来说:

    • 对于一个前面的数(j),从(i)(j)产生的代价是:( ext{pos}cdot kcdot ext{cnt}[i][j])
    • 对于一个前面的数(j),从(j)(i)产生的代价是:( ext{pos}cdot ext{cnt}[j][i])
    • 对于一个后面的数(j),从(i)(j)产生的代价是:(- ext{pos}cdot ext{cnt}[i][j])
    • 对于一个后面的数(j),从(j)(i)产生的代价是:( ext{pos}cdot kcdot ext{cnt}[j][i])

    所以我们也可以写出式子:

    [dp[s+i]leftarrow dp[s]+ ext{pos}cdot sum_{jin s}(kcdot ext{cnt}[i][j]+ ext{cnt}[j][i])+ ext{pos}cdotsum_{j otin(s+i)}(- ext{cnt}[i][j]+kcdot ext{cnt}[j][i]) ]

    如果枚举(i),再枚举(j),DP的时间复杂度(O(2^mm^2))。期望得70分。

    优化时间

    我们继续优化。考虑只枚举(i)。把( ext{pos})前的系数(也就是所有(j)的贡献之和),预处理出来,不妨记为:( ext{cost}(s,i))。那么上述的转移式,也可以改写为:(dp[s+i]leftarrow dp[s]+ ext{pos}cdot ext{cost}(s,i))

    考虑预处理( ext{cost}(s,i))。首先,根据定义,(i otin s)

    我们考虑,( ext{cost}(s,i)),可以从“(s)去掉某个数”的状态,转移过来。我们不妨就去掉(j= ext{lowbit}(s))(也就是二进制下最低的,为(1)的位)。那么,( ext{cost}(s,i)= ext{cost}(s-j,i)+(kcdot ext{cnt}[i][j]+ ext{cnt}[j][i])-(- ext{cnt}[i][j]+kcdot ext{cnt}[j][i]))

    这样转移是(O(1))的。所以预处理的时间复杂度为(O(2^mm)),DP的时间复杂度也降为(O(2^mm))。总时间复杂度(O(n+2^mm))。但是( ext{cost})数组会占用(O(2^mm))的空间,这无法承受。所以还要继续优化空间。

    优化空间

    这题优化空间的方法很多。我讲一种比较好想的。想了解更多方法,可以阅读这篇文章

    发现( ext{cost})( ext{dp})的转移,都是按集合从小到大进行的。所以我们不妨就按这个顺序,一边DP,一边求( ext{cost})

    对每个(s),我们把( ext{cost}(s,dots))视为一个大小为(m)的数组。当前的(s),我们先做DP转移,再拿( ext{cost}(sdots))数组去更新所有( ext{cost}(s'dots ))。发现更新完之后,( ext{cost}(s,dots))这个大小为(m)的数组,就可以不要了!

    如果能及时地把( ext{cost}(s,dots))这个数组,以某种方式“删除”掉(不再占用空间)。那么,在整个DP的过程中,同一时刻,我们存储的最多只有两种大小(s)。因为( ext{cost}(s,dots))只会转移到比它恰好大(1)(s')。那么空间的极限,就是({23choose 12}+{23choose 11})个大小为(m)的数组,约等于(237 ext{MB}),完全可以了。

    那么怎么实现这个“删除”呢?一种方法是用滚动数组。按大小枚举所有集合。每处理完一种大小,就把数组“滚一次”。另一种方法,是直接利用( exttt{stl})里的( exttt{queue}),向BFS一样,每次取出队首的(s)( exttt{pop})掉就行。

    空间复杂度,(displaystyle Oleft(2^m+mcdot{mchoosefrac{m}{2}} ight))。前面分析过,大约(237 ext{MB}),可以通过本题。

    参考代码(在LOJ查看):

    //problem:LOJ3302
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    inline void ckmin(int& x,int y){x=(x<y?x:y);}
    
    const int MAXN=1e5,MAXM=23;
    const int INF=1e9;
    int n,m,K,arr[MAXN+5],cnt[MAXM][MAXM];
    int id[1<<MAXM],bitcnt[1<<MAXM];
    int dp[1<<MAXM];
    
    struct State{
    	int s;
    	int v[MAXM];
    };
    
    int main() {
    	//freopen("transfer.in","r",stdin);
    	//freopen("transfer.out","w",stdout);
    	cin>>n>>m>>K;
    	for(int i=1;i<=n;++i){
    		cin>>arr[i];
    		arr[i]--;
    	}
    	for(int i=1;i<n;++i){
    		cnt[arr[i]][arr[i+1]]++;
    	}
    	
    	queue<State>q;
    	State s;
    	s.s=0;
    	for(int i=0;i<m;++i){
    		s.v[i]=0;
    		for(int j=0;j<m;++j)if(j!=i){
    			s.v[i]-=cnt[i][j];
    			s.v[i]+=K*cnt[j][i];
    		}
    	}
    	q.push(s);
    	
    	int S=(1<<m)-1;
    	for(int i=0;i<m;++i)id[1<<i]=i;
    	for(int i=1;i<=S;++i){
    		bitcnt[i]=bitcnt[i>>1]+(i&1);
    		dp[i]=INF;
    	}
    	while(!q.empty()){
    		State curs=q.front();q.pop();
    		assert(dp[curs.s]<INF);
    		int p=bitcnt[curs.s]+1;
    		for(int t=S^curs.s;t;t-=(t&(-t))){
    			//枚举curs.s的补集里的一个点i
    			int i=id[t&(-t)];
    			int new_dp=dp[curs.s];
    			int new_s=(curs.s|(1<<i));
    			new_dp+=p*curs.v[i];
    			
    			ckmin(dp[new_s],new_dp);
    		}
    		for(int i=0;i<m;++i){
    			if((curs.s>>i)&1)break;
    			State new_state;
    			new_state.s=(curs.s|(1<<i));
    			for(int t=S^new_state.s;t;t-=(t&(-t))){
    				//枚举new_state.s的补集里的一个点j 
    				int j=id[t&(-t)];
    				new_state.v[j]=curs.v[j]
    					+(K*cnt[j][i]+cnt[i][j])
    					-(-cnt[j][i]+K*cnt[i][j]);
    			}
    			q.push(new_state);
    		}
    	}
    	cout<<dp[S]<<endl;
    	return 0;
    }
    
  • 相关阅读:
    elasticsearch 心得
    elasticsearch window下配置安装
    centos 配置sentry+钉钉+邮件通知
    git 多账户链接不同gitlab仓库
    git 配置远程仓库(同一个邮箱注册多个gitlab仓库)
    配置git远程连接gitlab
    上传模型方法-断点续传方法
    three.js group遍历方法
    sql 行转列超快方法
    赴日本IT的相关注意事项和坑!!!!
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13201569.html
Copyright © 2020-2023  润新知