ABC232 计数dp 逆序对

D

可达性dp

只能往下往右,有障碍物,问能到的最远点。由于路径单调,不用搜索,直接可行性dp

char a[101][101];
int vis[101][101];
void solve(void){
	int n,m;
	cin>>n>>m;
	
	rep(i,1,n){
		rep(j,1,m){
			cin>>a[i][j];
		}
	}
	vis[1][1]=1;
	int ans=0;
	rep(i,1,n){
		rep(j,1,m){
			if(a[i][j]=='#')continue;
			if(i){
				vis[i][j]|=vis[i-1][j];
			}
			if(j){
				vis[i][j]|=vis[i][j-1];
			}
			if(vis[i][j]){
				ans=max(ans,i-1+j-1);
			}
		}
	}
	cout<<ans+1;
}

E

组合数学/计数dp

开始在一个点,每次可以移动到同行同列的任意位置,问移动k次后在另一个点的方案数

地图小的话可以dp记录走了k步,在每个位置的方案数。但是地图很大,那么从另一个角度来看,我们可以把所有点看成,初始点,和初始点同一行,和初始点同一列,和初始点不同行不同列,我们维护走了k步后在这四个区域的方案数是很简单的,复杂度也不大。并且其实每一个区域里的点的方案数都是一样的,都等于我们dp出来的值,因为我们dp是并没有指定到底在区域里的哪个点,只要在区域里就行。

所以最后看一下终点在哪个区域,输出对应方案即可

void solve(void){
	int n,m,k;
	cin>>n>>m>>k;
	
	int x1,x2,y1,y2;
	cin>>x1>>y1>>x2>>y2;
	
	vvi dp(4,vi(k+1));
	dp[0][0]=1;
	rep(i,1,k){
		dp[0][i]=(dp[1][i-1]*(m-1)+dp[2][i-1]*(n-1))%M2;
		dp[1][i]=(dp[0][i-1]+dp[3][i-1]*(n-1)+dp[1][i-1]*(m-2))%M2;
		dp[2][i]=(dp[0][i-1]+dp[3][i-1]*(m-1)+dp[2][i-1]*(n-2))%M2;
		dp[3][i]=(dp[1][i-1]+dp[2][i-1]+dp[3][i-1]*(n+m-4))%M2;
		
//		rep(j,0,3){
//			cout<<dp[j][i]<<' ';
//		}
//		cout<<'\n';
	}
	if(x1==x2&&y1==y2)cout<<dp[0][k];
	else if(x1==x2)cout<<dp[1][k];
	else if(y1==y2)cout<<dp[2][k];
	else cout<<dp[3][k];
}

F

状压dp 逆序对

两个等长序列,可以把一个元素加减一,代价x,可以交换一个序列中两个元素位置,代价y。问把两个序列变成一样的最小代价

注意到 n = 18 n=18 n=18,那么可以状压。考虑状压,假设mask有i个1,b序列不动,a序列选出mask里为1的这些元素,与b序列的的前i个元素对应,的最小代价。这里其实是利用了mask里的1的个数等于已选中元素个数,这样就能知道当前要选的是第几个元素,要和b序列中哪个元素对应

a中选一个未选中的元素和b中第i个元素对应,最后他们要相等,因此要用操作一把他们大小调到相等。并且要把这个元素从它在a序列中的原本位置,移动到当前位置,需要计算移动次数。

这个移动次数不太好想,开始我就是卡在这里了,因为你移动之后还会改变其他元素的位置,而我们要的是总移动次数最少。如果只考虑当前元素移动次数尽量少,可能总次数不一定最少。

这里要用到一个结论:每次只交换相邻元素的话,把一个排列升序排序所需的交换次数,就是初始的总逆序对个数。因为排序时,每个元素至少都要和所有与他构成逆序对的元素交换位置,这是交换次数的下界,不可能再少了。同时这个下界是可以取到的,在没排序完时,任意时刻总有两个相邻元素是逆序的,我们每次都交换相邻逆序对,这样对于每个逆序对,我们都只花了一次操作交换他们,总的操作次数就是逆序对个数。

回到本题,我们只要在dp过程中,计算逆序对个数,就是操作y的最小执行次数。然后这里的逆序对是指,在初始a中在当前元素后面,但是在选择方案中在当前元素前面。我们每次添加一个元素时,检查mask里有多少个序号比他大的即可

void solve(void){
	int n,x,y;
	cin>>n>>x>>y;
	
	vi dp(1<<n,1e18);
	dp[0]=0;
	vi a(n+1),b(n+1);
	rep(i,1,n){
		cin>>a[i];
	}
	rep(i,1,n){
		cin>>b[i];
	}
	
	rep(i,0,(1<<n)-1){
		int cnt=__builtin_popcount(i);
		rep(j,0,n-1){
			if(i>>j&1)continue;
			int rev=0;
			rep(k,0,n-1){
				if(i>>k&1){
					if(k>j){
						rev++;
					}
				}
			}
			dp[i|(1<<j)]=min(dp[i|(1<<j)],dp[i]+rev*y+x*abs(a[j+1]-b[cnt+1]));
		}
	}
	cout<<dp[(1<<n)-1];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值