脑子抽了记一记

给自己看的做题笔记,感觉写不太长就干脆直接合在一起了

P2851 [USACO06DEC]The Fewest Coins G

Date:2021.10.11
Tag:完全背包+多重背包
Solution:
这玩意一眼背包 但是我还真没想到两个背包
首先买家持有量有限,多重背包,记为\(dp_1\)
卖家持有量无限(所以他开店干嘛啊),完全背包,记为\(dp_2\)
最后状态合并一下,有:

\[ans=\min^{m + mx}_{i = m}(dp_1[i]+dp_2[i-m]) \]

关于式子里的\(mx\):

\[mx = \max^{n}_{i = 1}v[i]^2 \]

signed main() {
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	N = read(), T = read();
	
	for(int i = 1; i <= N; ++ i) v[i] = read(), mx = max(mx, v[i] * v[i]);
	
	for(int j = 1; j <= N; ++ j) c[j] = read(), sum += (c[j] * v[j]);
	
	if(sum < T) {
		cout << -1 << endl;
		return 0;
	}
	
	memset(dp1, 0x3f, sizeof dp1);
	memset(dp2, 0x3f, sizeof dp2);
	dp1[0] = dp2[0] = 0;
	
	for(int i = 1; i <= N; ++ i) {
		for(int j = v[i]; j <= mx; j ++) dp2[j] = min(dp2[j], dp2[j - v[i]] + 1);
	}
	
	for(int i = 1; i <= N; ++ i) {
		for(int j = 1; j <= c[i]; j <<= 1) {
			for(int k = T + mx; k >= j * v[i]; -- k) dp1[k] = min(dp1[k], dp1[k - j * v[i]] + j);
			c[i] -= j;
		}
		if(c[i]) for(int j = T + mx; j >= c[i] * v[i]; j --) dp1[j] = min(dp1[j], dp1[j - c[i] * v[i]] + c[i]);
	}
	
	for(int i = T; i <= T + mx; ++ i) ans = min(ans, dp1[i] + dp2[i - T]);
	
	cout << (ans == 0x3f3f3f3f ? -1 : ans) << endl;
//	fclose(stdin);fclose(stdout);
}

P7098 [yLOI2020] 凉凉

Date:2021.10.12
Tag:状压DP
Solution:
感觉这题和宝藏挺像的
数据范围为就很明显的告诉你个这是个状压
但是由于有深度因素,我们的状态有点不一样:定义 \(dp[i][sta]\) 表示在深度为 \(i\) ,车站的选择状态为 \(sta\) 时的最优解。
就有转移方程:

\[dp[i][sta] = \min_{s⊆sta}(dp[i - 1][s]+val(s->sta)) \]

转移方程的意义:
枚举子集区分新建在这一深度和之前建的,然后val表示当前要建设在该深度的所有线路的价值
同时我们还知道一种神奇的枚举子集的方法

for(int s = sta; s; s = (s - 1) & sta)

这样优化一波,复杂度就不是问题了

#include<bits/stdc++.h>
#define int long long
//#define rint register int
#define endl '\n'
#define ITV vector< int > :: iterator
using namespace std;
const int maxn = 1e5 + 5,mod = 1e9 + 7;
const int inf = 0x3f3f3f3f3f3f3f3f;

inline int read() {
    char c=getchar(),f=0;int t=0;
    for(;c<'0'||c>'9';c=getchar()) if(!(c^45)) f=1;
    for(;c>='0'&&c<='9';c=getchar()) t=(t<<1)+(t<<3)+(c^48);
    return f?-t:t;
}

inline void write(int num) {
	if(num < 0) {
		putchar('-');
		num = -num;
	}
	if(num > 9) write(num / 10);
	putchar(num % 10 + '0');
}

int n, m;
int a[15][maxn];
int f[15][1 << 15]; // 表示我在当前深度的状态下的最优话费
vector< int > road[maxn];
int line_val[15][15]; //表示第i条路线在深度为j的总建设费
bool can[15][15]; // 表示i 能不能和 j 一起建设
int bet_val[15][1 << 15];// 表示这个集合的话费
int tool[15]; // 工 具 数 组

signed main() {
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = read(), m = read();
	
	for(int i = 1; i <= n; ++ i) {
		for(int j = 1; j <= m; ++ j) {
			a[i][j] = read();
		}
	}
	
	for(int i = 1, x, y; i <= n; ++ i) {
		x = read();
		for(int j = 1; j <= x; ++ j) {
			y = read();
			road[i].push_back(y);
		}
		sort(road[i].begin(), road[i].end());
		for(int j = 1; j <= n; ++ j) {
			for(ITV k = road[i].begin(); k != road[i].end(); k ++) {
				line_val[i][j] = line_val[i][j] + a[j][*k];
			}
		}
	}
	/*对于路线总费用的预处理*/
	for(int i = 1; i <= n; ++ i) {
		for(int j = i + 1; j <= n; ++ j) {
			can[i][j] = can[j][i] = true;
			
			ITV l = road[i].begin(), r = road[j].begin();
			
			while(l != road[i].end() && r != road[j].end()) {
				if(*l == *r) {
					can[i][j] = can[j][i] = false; break;
				}
				if(*l < *r) ++l;
				else ++r;
			}
		}
	}
	/*对于两条路径可建设性的预处理*/
	for(int sta = 0; sta < (1 << n); ++ sta) {
		int cnt = 0;
		for(int i = 1; i <= n; ++ i) {
			if(sta & (1 << (i - 1))) {
				tool[++cnt] = i;
				for(int j = 1; j < cnt; ++ j) {
					if(can[tool[j]][i] == false) {
						for(int k = 1; k <= n; ++ k) bet_val[k][sta] = inf;
						break;
					}
				}
				if(bet_val[1][sta] == inf) break;
				for(int k = 1; k <= n; ++ k) {
					bet_val[k][sta] += line_val[i][k];
				}
			}
		}
	}
	
	memset(f, 0x3f, sizeof f);
	
	for(int sta = 0; sta < (1 << n); ++ sta) {
		f[1][sta] = bet_val[1][sta];
	}
	
	for(int i = 2; i <= n; ++ i) {
		for(int sta = 0; sta < (1 << n); ++ sta) {
			f[i][sta] = f[i - 1][sta];
			for(int s = sta; s; s = (s - 1) & sta) {
				f[i][sta] = min(f[i][sta], f[i - 1][sta ^ s] + bet_val[i][s]);
			}
		}
	}
	
	cout << f[n][(1 << n) - 1] << endl;
//	fclose(stdin);fclose(stdout);
}

P4042 [AHOI2014/JSOI2014]骑士游戏

Date:2021.10.12
Tag:最短路(凸轮真是个神奇的东西)
Solution:
我们分析每个怪:我选择直接干碎?还是选择留他一命?
这看起来很DP,但是你会发现他有后效性(绝大可能出现环)
所以我们把SPFA改一波:我们在原图的基础上建一个反图,每次通过原图计算贡献,通过反图入队更新可以咯!

#include<bits/stdc++.h>
#define int long long
//#define rint register int
#define endl '\n'
using namespace std;
const int maxn = 2e5 + 5,mod = 1e9 + 7;

inline int read() {
    char c=getchar(),f=0;int t=0;
    for(;c<'0'||c>'9';c=getchar()) if(!(c^45)) f=1;
    for(;c>='0'&&c<='9';c=getchar()) t=(t<<1)+(t<<3)+(c^48);
    return f?-t:t;
}

inline void write(int num) {
	if(num < 0) {
		putchar('-');
		num = -num;
	}
	if(num > 9) write(num / 10);
	putchar(num % 10 + '0');
}

int n;
vector< int > pic[maxn], ipic[maxn];
int a[maxn], dis[maxn], vis[maxn];

inline void add(int x, int y) {
	pic[x].push_back(y);
	ipic[y].push_back(x);
}

inline void SPFA() {
	queue< int > q;
	for(int i = 1; i <= n; ++ i) q.push(i), vis[i] = 1;
	while(q.size()) {
		int now = q.front(); q.pop();vis[now] = 0;
		
		int tem = a[now];
		
		for(int i = 0; i < pic[now].size(); ++ i) tem += dis[pic[now][i]];
		if(tem >= dis[now]) continue;
		dis[now] = tem;
		for(int i = 0; i < ipic[now].size(); ++ i) {
			if(!vis[ipic[now][i]]) {
				vis[ipic[now][i]]= 1;
				
				q.push(ipic[now][i]);
			}
		}
	}
}

signed main() {
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = read();
	
	for(int i = 1, x; i <= n; ++ i) {
		a[i] = read(), dis[i] = read(), x = read();
		for(int _ = 1, y; _ <= x; ++ _) {
			y = read();
			add(i, y);
		}
	}
	
	SPFA();
	
	cout << dis[1] << endl;
//	fclose(stdin);fclose(stdout);
}

posted @ 2021-10-11 20:03  I_got_light  阅读(46)  评论(0)    收藏  举报