AT_dp题目整理

目录

Frog 1(AT_dp_a)

Frog 2(AT_dp_b)

Vacation(AT_dp_c)

Knapsack 1(AT_dp_d)

Knapsack 2(AT_dp_e)

LCS(AT_dp_f)

Longest Path(AT_dp_g)

Grid 1(AT_dp_h)

Coins(AT_dp_i)

Sushi(AT_dp_j)

Stones(AT_dp_k)

Candies(AT_dp_m)

Slimes(AT_dp_n)

Matching(AT_dp_o)

Independent Set(AT_dp_p)

Walk(AT_dp_r)


Frog 1(AT_dp_a)

题意简述

        有n块石头,第i块石头的高度为h_i.

        一只青蛙从第1块石头开始跳跃,它可以往后跳一个或两个石头,且花费为\vert {h_i -h_j} \vert

        求当跳到第n块时,总花费的最小值.

思路

        很容易想到状态转移方程 : dp_i = min(dp_{i-1} + \vert h_i - h_{i-1} \vert , dp_{i-2} + \vert h_i - h_{i-2} \vert)

        递推至第n项后输出即可.

代码

        

#include<bits/stdc++.h>
using namespace std;
int n;
int h[100005] , dp[100005];
int main(){
	cin >> n ;
	for(int i = 1 ; i <= n ; i++) {
		cin >> h[i];
	}
	dp[1] = 0 , dp[2] = abs(h[2] - h[1]);
	for(int i = 3 ; i <= n ; i++) {
		dp[i] = min(dp[i-2] + abs(h[i] - h[i-2]) , dp[i-1] + abs(h[i] - h[i-1]));
	}
	cout << dp[n];
	return 0;
}

Frog 2(AT_dp_b)

题意简述

        有n块石头,第i块石头的高度为h_i.

        一只青蛙从第1块石头开始跳跃,它可以往后 1k 个石头,且花费为\vert {h_i -h_j} \vert

        求当跳到第n块时,总花费的最小值.

思路

        与上道题的不同点是转移项数变多了,所以需要对第i-ki-1项进行遍历

       转移方程 : dp_i = min\{dp_{i-k} + \vert h_i - h_{i-k} \vert , ... , dp_{i-1} + \vert h_i - h_{i-1} \vert\}

        递推至第n项后输出即可.

代码

#include<bits/stdc++.h>
using namespace std;
int n , k;
int h[100005] , dp[100005];
int main(){
	memset(dp, 0x3f , sizeof dp);
	cin >> n >> k;
	for(int i = 1 ; i <= n ; i++) {
		cin >> h[i];
	}
	dp[1] = 0;
	for(int i = 2 ; i <= n ; i++) {
		for(int j = max(0 , i - k) ; j <= i - 1 ; j++) {
			dp[i] = min(dp[i] , dp[j] + abs(h[j] - h[i]));
		}
	}
	cout << dp[n];
	return 0;
}

Vacation(AT_dp_c)

题意简述

        给定n天,太郎在每天可以进行三种操作:

        其中,在第n天操作1可增加a_i点快乐值 , 操作2可增加b_i点快乐值 , 操作3可增加c_i点快乐值

        需要注意,太郎不能连续两天进行相同的操作

        求n天结束后 , 太郎快乐值总值的最大值

思路

        属于基础dp ,考虑为 dp 数组增加一维

        令 dp_{i , j} 表示第 j 天进行 i 操作的情况下所获得快乐值总值的最大值

        则显然可以得到状态转移方程 :

         dp_{0, i} = max(dp_{1, i -1} , dp_{2 , i-1}) + a_i

         dp_{1, i} = max(dp_{0, i -1} , dp_{2 , i-1}) + b_i

         dp_{2, i} = max(dp_{0 , i -1} , dp_{1 , i-1}) + c_i

代码

        

#include<bits/stdc++.h>
using namespace std;
int n;
int dp[3][100005];
int a[100005] , b[100005] , c[100005];
int main(){
	cin >> n ;
	for(int i = 1 ; i <= n ; i++){
		cin >> a[i] >> b[i] >> c[i];
		dp[0][i] = max(dp[1][i-1] , dp[2][i-1]) + a[i];
		dp[1][i] = max(dp[0][i-1] , dp[2][i-1]) + b[i];
		dp[2][i] = max(dp[1][i-1] , dp[0][i-1]) + c[i];
	}
	cout << max(dp[0][n] , max(dp[1][n] , dp[2][n]));
	return 0;
}

Knapsack 1(AT_dp_d)

题意简述

        给定 n 件物品 , 每件物品的价值为v_i , 重量为 w_i  , 从中选择若干件物品 , 且总重量不超过M , 求总价值的最大值。

思路

        属于很经典的背包dp , 正常01背包即可 ,令 dp_{i , j} 表示选到第i件物品且总重为j时的最大价值,状态转移方程如下:

                                        dp_{i , j} = max(dp_{i-1 , j } ,dp_{i-1 , j-w_i}+v_i)

代码

        

#include<bits/stdc++.h>
using namespace std;
int n , W;
long long dp[105][100005];
long long a[105] , w[105];
int main(){
	cin >> n >> W;
	for(int i = 1 ; i <= n ; i++){
		cin >> w[i] >> a[i];
	}
	for(int i = 1 ; i <= n ; i++){
		for(int j = W ; j >= 0 ; j--){
			if(j >= w[i]){
				dp[i][j] = max(dp[i-1][j-w[i]] + a[i] , dp[i-1][j]);
			}else{
				dp[i][j] = dp[i-1][j];
			}
		}
	}
	cout << dp[n][W];
	return 0;
}

 Knapsack 2(AT_dp_e)

题意简述

        给定 n 件物品 , 每件物品的价值为v_i , 重量为 w_i  , 从中选择若干件物品 , 且总重量不超过M , 求总价值的最大值。( W\leq 10^9 , V \leq 10^3 )

思路

        相比上道题 ,这道题中 W 的范围提高到了 10^9 ,这意味着我们无法通过枚举 W 来解决 , 但同时 V 的数量级降低到了10^3 , 所以我们可以考虑令 dp_{i , j} 表示枚举到第 i 个物品时令价值为j时的最小重量 , 则可以得到状态转移方程:

                                        dp_{i , j } = min(dp_{i -1 ,j } , dp_{i-1 , j-v_i}+w_i)

最后对于dp_n进行倒序枚举 , 判断是否合法即可。

代码

#include<bits/stdc++.h>
using namespace std;
int n ;
long long W;
long long dp[105][100005];
long long w[105] , v[105];
long long V;
int main(){
	memset(dp , 0x3f , sizeof dp);
	cin >> n >> W;
	for(int i = 1 ; i <= n ; i++){
		cin >> w[i] >> v[i];
		V += v[i];
	}
	dp[0][0] = 0;
	for(int i = 1 ; i <= n ; i++){
		for(int j = V ; j >= 0 ; j--){
			if(j >= v[i]){
				dp[i][j] = min(dp[i-1][j] , dp[i-1][j-v[i]] + w[i]);
			}else{
				dp[i][j] = dp[i-1][j];
			}
		}
	}
	for(int i = V ; i >= 0 ; i--){
		if(dp[n][i] <= W){
			cout << i ;
			return 0;
		}
	}
	return 0;
}

 LCS(AT_dp_f)

题意简述

        给定两个字符串 s 和 t ,求出它们的最长公共子序列。

思路

       很经典的线性dp题目 , 这道题我们可以考虑先求出LCS的长度 , 再根据其找到LCS。

        我们可以令 dp_{i , j} 表示 s 的前 i 项 和 t 的前 j 项的 LCS 长度

        那么很显然 , 状态转移方程 : dp_{i , j } = \displaystyle \left\{\begin{matrix} dp_{i-1 , j-1} + 1 & s_i = t_j& \\max(dp_{i-1 , j } , dp_{i , j - 1}) & & s_i \neq t_j \end{matrix}\right.

        由此 , dp_{n , m} 即为LCS的长度。

        求出长度后 , 我们可以考虑进行反向DFS , 通过状态转移方程反向推导即可。

代码

#include<bits/stdc++.h>
using namespace std;
int dp[3005][3005];
int len1 , len2 ;
string s , t;
void dfs(int i , int j){
	if(i == 0 || j == 0 || dp[i][j] == 0){
		return;
	}
	if(s[i] == t[j]){
		dfs(i - 1 , j - 1);
		cout << s[i];
	}
	else if(dp[i-1][j] == dp[i][j]) dfs(i - 1 , j);
	else dfs(i , j - 1); 
}
int main(){
	cin >> s >> t;
	len1 = s.size();
	len2 = t.size();
	s = ' ' + s;
	t = ' ' + t;
	for(int i = 1 ; i <= len1 ; i++){
		for(int j = 1 ; j <= len2 ; j++){
			if(s[i] == t[j]) dp[i][j] = dp[i-1][j-1] + 1;
			else dp[i][j] = max(dp[i-1][j] , dp[i][j-1]);
		}
	}
	dfs(len1 , len2);
	return 0;
}

Longest Path(AT_dp_g)

题意简述

        给出 n个顶点 m条边的有向无环图 G , 求出该图中最长的路径长度。

思路

       题目要求最长的路径长度 , 我们可以考虑对其进行分层 , 固定一个节点为最顶层 , 那么当走到最底层所走路径长度自然就是最长长度 , 对图上的每一个子图分别拓扑即可。

代码

​
#include<bits/stdc++.h>
using namespace std;
int n , m , ans;
int rd[100005];
int head[100005] , ne[100005] , ver[100005] , tot = -1 , dist[100005];
bool vis[100005];
void add(int x , int y){
	tot++;
	ver[tot] = y;
	ne[tot] = head[x];
	head[x] = tot;
}
void topsort(int x){
	queue<int> q;
	q.push(x);
	vis[x] = 1;
	dist[x] = 0;
	while(!q.empty()){
		int u = q.front();
		q.pop();
		for(int i = head[u] ; i != -1 ; i = ne[i]){
			int v = ver[i];
			vis[v] = 1;
			dist[v] = max(dist[v] , dist[u] + 1);
			//cout << v << " " << dist[v] << " " << u << " " << dist[u] << "\n";
			rd[v]--;
			if(rd[v] == 0) q.push(v); 
		}
	}
}
int main(){
	memset(head, -1,sizeof head);
	cin >> n >> m ;
	for(int i = 1 ; i <= m ; i++){
		int x , y;
		cin >> x >> y ;
		rd[y] ++;
		add(x , y);
	}
	for(int i = 1 ; i <= n ; i++){
		if(rd[i] == 0 && !vis[i]){
			topsort(i);
		} 
	}
	for(int i = 1 ; i <= n ; i++){
		ans = max(ans , dist[i]);
	}
	cout << ans;
	return 0;
}

​

Grid 1(AT_dp_h)

题意简述

       给出一个HW列的矩阵 , 包含两种元素 "." 表示可以走 , "\#"表示不能走,求从(1 , 1)走到 (H , W) 的路径总数。

思路

       比较基础的二维 , 可以令 dp_{i , j} 表示走到第 i 行 j 列时的路径总数。

        则很简单可以获得状态转移方程 dp_{i , j } = \left\{\begin{matrix} dp_{i -1 ,j} + dp_{ i , j-1} &a_{i ,j} = '.' & \\ 0 & & a_{i , j} ='\#' \end{matrix}\right.

代码

#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9+7;
int n , m ;
char ma[1005][1005];
long long dp[1005][1005];
int main(){
	cin >> m >> n;
	for(int i = 1 ; i <= m ; i++){
		for(int j = 1 ; j <= n ; j++){
			cin >> ma[i][j];
		}
	}
	dp[1][1] = 1;
	for(int i = 1 ; i <= m ; i++){
		for(int j = 1 ; j <= n ; j++){
			if(i == 1 && j == 1) continue;
			if(ma[i][j] == '#') continue;
			dp[i][j] = dp[i][j-1] + dp[i-1][j];
			dp[i][j] %= mod;
		}
	}
	cout << dp[m][n];
	return 0;
}

Coins(AT_dp_i)

题意简述

        给定一个正奇数 N , 有 N 枚硬币 , 其中第 i 枚硬币出现正面的概率为 p_i ,出现反面的概率是1-p_i ,求正面个数多于反面的次数。

思路

        比较经典的数学期望 dp ,我们可以考虑令 dp_{i , j}表示前 i 个硬币有 j 个向上的概率 ,那么状

态转移方程很显然 ,              dp_{i , j } = dp_{i - 1 , j-1} \times p_i + dp_{i-1 , j} \times(1-p_i)

求得dp后按题意输出即可。

代码

       

#include<bits/stdc++.h>
using namespace std;
int n;
double p[3005] , dp[3005][3005];
int main(){
	cin >> n ;
	for(int i = 1 ; i <= n ; i++){
		cin >> p[i];
	}
	dp[0][0] = 1;
	for(int i = 1 ; i <= n ; i++) {
		for(int j = 0 ; j <= n ; j++) {
			dp[i][j] = dp[i - 1][j] * (1 - p[i]) + dp[i - 1][j - 1] * p[i];
		}
	}
	double ans = 0;
	for(int i = n / 2 + 1 ; i <= n ; i++){
		ans += dp[n][i];
	}
	cout << fixed << setprecision(10) << ans;
	return 0;
}

Sushi(AT_dp_j)

题意简述

        给出 N 道菜 , 其中第 i 道菜上有 a_i 块寿司 (1 \leq a_i \leq 3) 。

        从1\sim N中随机抽取一个数 i ,如果第 i 道菜上还有寿司 , 就吃掉一块 , 否则就不吃。

        求吃完所有寿司的期望操作次数。

思路

      也是一道数学期望dp ,在这道题中 , 显然普通的dp会超时 , 所以我们考虑以空间换时间 , 对dp数组进行换维 ,令dp_{i , j , k} 剩余 i 盘 1 个的寿司 , j 盘 2 个的寿司  ,k 盘 3 个的寿司时的期望操作次数 , 那么可以得到一个很长的状态转移方程 :

        ​​​​​​​        dp_{i , j , k} = \frac{(n-i-j-k)dp_{i , j , k} + idp_{i-1 , j , k}+jdp_{i+1 , j-1 , k}+kdp_{i , j+1 , k-1}+n}{n}

我们对其进行化简可以得到:dp_{i , j , k} =\frac{idp_{i-1, j , k} + jdp_{i+1 , j-1 , k} +kdp_{i , j+1 , k-1}+n}{i+j+k}

遍历求解即可。

代码

#include<bits/stdc++.h>
using namespace std;
int n;
double dp[305][305][305];
int cnta , cntb , cntc;
int plate[305];
int main(){
	cin >> n ;
	for(int i = 1 ; i <= n ; i++) {
		cin >> plate[i];
		if(plate[i] == 1) cnta ++;
		if(plate[i] == 2) cntb ++;
		if(plate[i] == 3) cntc ++; 
	}
	for(int k = 0 ; k <= n ; k++){
		for(int j = 0 ; j <= n ; j++){
			for(int i = 0 ; i <= n ; i++){
				if(i+j+k == 0) continue;
				if(i) dp[i][j][k] += dp[i-1][j][k] * i / (i + j + k); 
				if(j) dp[i][j][k] += dp[i+1][j-1][k] * j / (i + j + k);
				if(k) dp[i][j][k] += dp[i][j+1][k-1] * k / (i + j + k);
				dp[i][j][k] += 1.0*n / (i + j + k);
			}
		}
	}
	cout << fixed << setprecision(10) << dp[cnta][cntb][cntc];
	return 0;
}

Stones(AT_dp_k)

题意简述

       给定 N 个正整数 a_1 , a_2 .....a_n , 有 K 块石头,两个玩家轮流选择一个正整数 a_i , 并从石头中移除 a_i 块 , 当玩家无法进行操作时判负 , 求先手能否获胜。

思路

      这是一道博弈论dp , 我们可以从胜负状态出发思考 , 当当前剩余 0 块石头时 ,当前操作者一定必败 , 所以如果当前的状态可以转化为必败状态 , 当前操作者一定必胜。

那么我们可以令dp_i表示剩余 i 块石头时当前操作者的胜负 , 那么显然 dp_i 为必胜当且仅当存在a_j \leq i 且 dp_{i-a_j}为必败。

代码

#include<bits/stdc++.h>
using namespace std;
int n , k ;
int a[105];
bool dp[100005];
int main(){
	cin >> n >> k;
	for(int i = 1 ; i <= n ; i++){
		cin >> a[i];
	}
	for(int i = 1 ; i <= k ; i++){
		for(int j = 1; j <= n ; j++){
			if(i - a[j] < 0) continue;
			dp[i] |= !dp[i - a[j]];
		}
	}
	if(dp[k] == 1) cout << "First";
	else cout << "Second";
	return 0;
}

Candies(AT_dp_m)

题意简述

       有 N 个孩子分 K 颗糖果 , 其中第 i 个孩子必须要分到 0\sim a_i 颗糖果 , 求分享糖果的总方案数 , 并mod 10^9+7 。

思路

      很经典的前缀和优化 dp ,我们很容易可以想到朴素的dp状态 ,令 dp_{i, j}表示考虑到第 i 个孩子 ,已经用了 j 颗糖的方案数 ,则显然有 dp_{i , j} = \sum_{k = max(0 , j- a_{i-1})}^{j} dp_{i-1, k}

注意到 , 我们可以用前缀和sum优化掉连续的 dp 值之和 , 就有dp_{i , j }= sum_j - sum_{j-a_i-1}.

代码

#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9+7;
int n , k;
long long a[105];
long long sum[100005];
long long dp[105][100005];
int main(){
	dp[0][0] = 1;
	cin >> n >> k ;
	for(int i = 1 ; i <= n ; i++) {
		cin >> a[i];
	}
	sum[0] = 1;
	for(int i = 1 ; i <= k ; i++) {
		sum[i] = sum[i - 1];
		sum[i] %= mod;
	}
	for(int i = 1 ; i <= n ; i++) {
		for(int j = 0 ; j <= k ; j++) {
			int l = max(0ll , j - a[i]);
			if(l <= 0) dp[i][j] = sum[j];
			else dp[i][j] = sum[j] - sum[l - 1] + mod;
			dp[i][j] %= mod;
		}
		sum[0] = dp[i][0];
		for(int j = 1 ; j <= k ; j++) {
			sum[j] = sum[j - 1] + dp[i][j];
			sum[j] %= mod;
		}
	}
	cout << dp[n][k];
	return 0;
}

Slimes(AT_dp_n)

题意简述

      有 n 个数 , 要进行 n - 1 次操作 , 每次操作会选择相邻的两个数进行合并求和 , 操作的代价也为这个和  ,求总小的总代价。

思路 

     这是一道比较常规的区间dp ,我们可以令 dp_{i , j} 表示 a_i 到 a_j 合并的最小代价 , 我们可以外层枚举区间长度 , 第二层枚举左端点 , 求出右端点后枚举分割点 k , 则很显然可以获得状态转移方程                         dp_{i , j } = min(dp_{i , j } , dp_{i , k }+dp_{k , j }+\sum_{l = i}^{j}a_l)

由此求出 dp_{1 , n } 即可。

代码

#include<bits/stdc++.h>
using namespace std;
int n;
long long a[405],dp[405][405];
long long sum[405];
int main(){
	for(int i=1;i<=400;i++){
		for(int j=1;j<=400;j++){
			dp[i][j]=LONG_LONG_MAX;
		}
	}
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sum[i]=sum[i-1]+a[i];
		dp[i][i]=0;
	}
	for(int len=2;len<=n;len++){
		for(int left=1;left+len-1<=n;left++){
			int right=left+len-1;
			for(int k=left;k<right;k++){
				dp[left][right]=min(dp[left][right],dp[left][k]+dp[k+1][right]+sum[right]-sum[left-1]);
			}
		}
	}
	cout<<dp[1][n];
	return 0;
}

Matching(AT_dp_o)

题意简述

       给定二分图 , 两个集合都有 N 个点 , a_{i , j } = 1 表示第一个集合第 i 个点 与第二个集合第 j

个点连边 。求二分图完备匹配数  ,答案对 10^9+7取模。

思路 

       这是一道较有难度的状压dp题目 , 我们可以考虑枚举 i 个点和当前集合 j 中点的匹配数量 , 如果枚举成功 , 就更新答案和集合 , 同时答案要加上上上个集合的数量。

        显然 , 想要匹配成功需要满足两个条件:

        1.当前的点与集合 j 中的点有连边

        2.当前的点与集合中的点都没有被匹配过

我们可以设 dp_{i , j} 表示 i 个集合 1 中的点与集合 2 中的点所构成的集合 j 的匹配数量 ,令 cnt_j 表示集合中已经有的女生个数 , 我们可以选择枚举男生个数和女生集合 , 则要满足 cnt_j = i 才能继续更新  。

此时再找一个集合 2 中的点 k ,则要满足 a_{i , k} = 1 且j \cap k = 0

那么我们就可以得到状态转移方程 :dp_{i + 1, j\cup k } = dp_{i + 1 , j\cup k} + dp_{i , j}

最终答案即为 dp_{n , 2^n -1} ,输出即可。

代码

#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9+7;
int n;
int a[25][25];
long long dp[25][1<<21];
int main(){
	cin >> n ;
	for(int i = 0 ; i < n ; i ++){
		for(int j = 0 ; j < n ; j++){
			cin >> a[i][j];
		}
	}
	dp[0][0] = 1;
	for(int i = 0 ; i < n ; i++) {
		for(int j = 0; j < (1<<n) ;j++){
			if(__builtin_popcount(j) == i){
				for(int k = 0 ; k < n ; k++) {
					if(a[i][k] && (j & (1<<k)) == 0 ){
						dp[i + 1][j | (1<<k)] = (dp[i + 1][j | (1<<k)] + dp[i][j]) %mod;
					}
				}
			}
		}
	}
	cout << dp[n][(1 << n) - 1];
	return 0;
}

Independent Set(AT_dp_p)

题意简述

       给定一棵 N 个节点的树,每一个点可以染成黑色或白色,任意两个相邻节点不能都是黑色,求方案数,结果对 10^9+7取模。

思路 

      比较基础的树形dp ,我们可以默认节点 1 为树根,令 dp_{a,0} 表示节点 a 染白色时的方案数 , 令 dp_{a , 1} 表示节点 a 染黑色时的方案数 , 那么状态转移方程不难推出:

         ​​​​​​​        dp_{a , 0 }=\prod_{b\in a}^{} (dp_{b , 0} + dp_{b , 1})                     dp_{a , 1} =\prod_{b\in a}^{} dp_{b , 0}

那么在树上进行 dfs 同时求解即可。

代码

#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
int n;
int tot = -1 , head[200005] , ne[200005] , ver[200005];
long long dp[100005][2];
void add(int x , int y){
	tot++;
	ver[tot] = y;
	ne[tot] = head[x];
	head[x] = tot;
}
void dfs(int root , int father){
	dp[root][0] = dp[root][1] = 1;
	for(int i = head[root] ; i != -1 ; i = ne[i]){
		int x = ver[i];
		if(ver[i] == father) continue;
		dfs(ver[i] , root);
		dp[root][0] *= (dp[ver[i]][0] + dp[ver[i]][1]);
		dp[root][0] %= mod;
		dp[root][1] *= dp[ver[i]][0];
		dp[root][1] %= mod;
	}
}
int main(){
	memset(head, -1 , sizeof head);
	cin >> n ;
	for(int i = 1 ; i < n ; i++) {
		int x , y ;
		cin >> x >> y;
		add(x , y);
		add(y , x);
	}
	dfs(1 , 0);
	cout << (dp[1][0] + dp[1][1]) % mod;
	return 0;
}

Walk(AT_dp_r)

题意简述

       给出一张 N 个点有向简单图,给出邻接矩阵,求长度为 K 的路径条数,答案对 10^9+7 取模。

思路 

        比较典型的矩阵加速题目 , 对于原邻接矩阵 , 如果我们令 f_{t , i , j} 表示从 i 到 j 长度为 t 的路径条数 , 那么显然有        ​​​​​​​        f_{t , i , j} = \sum_{k=1}^{n} f_{k-1 , i , k } \times f_{1 , k , j}

就可以推导出来答案为                          \sum_{i=1}^{n}\sum_{j=1}^{n} f_{k , i , j}

但是显然时间复杂度超标 , 但是观察递推式我们可以发现 , 它相当于对邻接矩阵进行了一个矩阵乘法,又因为矩阵乘法满足结合律 , 所以我们就可以用矩阵快速幂对其进行优化。

设邻接矩阵为 P , 则答案即为 P^t 中所有数字之和。

代码

#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9+7; 
int n;
long long k;
struct Matrix{
	long long a[105][105];
	int r , c ;
};
Matrix operator *(Matrix A , Matrix B){
	Matrix C;
	C.r = n , C.c = n;
	memset(C.a , 0 , sizeof(C.a));
	for(int i = 1 ; i <= A.r ; i++){
		for(int j = 1 ; j <= B.c ; j++){
			for(int k = 1 ; k <= A.c ; k++){
				C.a[i][j] += (A.a[i][k] * B.a[k][j] % mod);
				C.a[i][j] %= mod;
			}
		}
	}
	return C;
}
Matrix init(Matrix A){
	for(int i = 1 ; i <= A.r ; i++){
		for(int j = 1 ; j <= A.c; j++){
			A.a[i][j] = (i == j);
		}
	}
	return A;
}
Matrix quick_Power(Matrix A , long long k){
	Matrix ans;
	ans.c = n , ans.r = n;
	ans = init(ans);
	while(k){
		if(k&1){
			ans = ans * A; 
		}
		A = A * A;
		k = k >> 1;
	}
	return ans;
}
int main(){
	Matrix A , T;
	cin >> n >> k; 
	A.c = n , A.r = n , T.r = n , T.c = n;
	for(int i = 1 ; i <= n ; i++) {
		for(int j = 1 ; j <= n ; j++) {
			cin >> A.a[i][j];
		}
	}
	T = quick_Power(A , k);
	long long ans = 0 ;
	for(int i = 1 ; i <= T.r ; i++) {
		for(int j = 1 ; j <= T.c ; j++) {
			ans += T.a[i][j];
			ans %= mod;
		}
	}
	cout << ans;
	return 0;
}

Grouping(AT_dp_u)

题意简述

       对 N 个物品进行分组 ,如果第 i 个物品和第 j 个物品分在一组 , 会产生 a_{i, j} 的得分 ,要求最大化得分之和 , 其中 a_{i, j}a_{j , i} 只计算一次。

思路 

       这也是一道状压dp的题目 , 相较 AT\_dp\_o 更为简单 , 我们可以考虑设 dp_i 为集合 i 的最大得分 , 那么可以考虑先计算出不划分集合情况下的总得分 , 再对其进行枚举子集 ,就可以得到状态转移方程:          dp_i = max_{j\in i} \{ dp_j +dp_{i-j}\} 其中 dp_{i - j} 表示 j 对于全集i的补集。

        可以思考一下,在这个状态转移方程中我们需要逐个枚举子集并判断,显然是有些冗余的,是否有方法可以直接枚举子集呢 , 这里给出一条循环语句 :

                                for(int \ j = i \& (i-1) ; j;j=i\&(j-1))

在这条语句中 , j = j\&(i-1) 保证了 j 的值严格递减 , 同时保证了j \in i ,所以就不重不漏的完成了对子集的枚举。

代码

#include<bits/stdc++.h>
using namespace std;
int n;
long long dp[1 << 20];
long long v[1 << 20];
long long a[20][20];
int main(){
	cin >> n ;
	for(int i = 0 ; i < n ; i++) {
		for(int j = 0 ; j < n ; j++) {
			cin >> a[i][j];
		}
	}
	for(int k = 1 ; k < (1 << n) ; k++){
		for(int i = 0 ; i < n ; i++) {
			if((k >> i) & 1){
				for(int j = i + 1 ; j < n ; j++) {
					if((k >> j) & 1){
						v[k] += a[i][j];
					}
				}
			}
		}
		dp[k] = v[k];
	}
	for(int i = 0 ; i < (1 << n) ; i++){
		for(int j = i & (i - 1) ; j ; j = i & (j - 1)) {
			dp[i] = max(dp[i] , dp[j] + dp[j ^ i]);
		}
	}
	cout << dp[(1 << n) - 1];
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值