2022“杭电杯”中国大学生算法设计超级联赛(4) 题解

\(dp[i][j]\) 表示区间 \([i,j]\) 有多少个匹配方案。

对于 \(AB\) 类情况,有 \(dp[i][j] = \sum dp[i][k] \cdot dp[k + 1][j]\)

对于 \((A)\) 类情况,有 \(dp[i][j] = dp[i+1][j-1] \cdot k\),其中 \(k\) 的情况由 \(a[i]\)\(a[j]\) 决定,取值范围为 \(0, \ 1, \ m\)

发现直接dp,对于\(AB\) 类会有重复情况,对于串 \(ABC\) 的dp值,我们可能分别从 \(A+BC\)\(AB+C\) 递推过来,会导致重复计数。

我们将上述两种情况分开,并规定只能从右边接新的括号串,于是就可以继续dp了。

#include<bits/stdc++.h>
#define int long long
#define eps 1e-12
using namespace std;

const int MAXN = 500 + 5;
const int MOD = 1e9 + 7;

int n, m, a[MAXN], dp[3][MAXN][MAXN];

void Solve() {
	scanf("%lld%lld", &n, &m);
	for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
	for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) dp[0][i][j] = dp[1][i][j] = 0;
	for(int i = 1; i < n; i++) {
		if(a[i] == 0 && a[i + 1] == 0) dp[0][i][i + 1] = m;
		else if(a[i] == -a[i + 1] && a[i] > 0) dp[0][i][i + 1] = 1;
		else if( (!a[i] && a[i + 1] < 0) || (!a[i + 1] && a[i] > 0) ) dp[0][i][i + 1] = 1;
		else dp[0][i][i + 1] = 0; 
	}
	for(int k = 3; k <= n; k++) {
		for(int i = 1; i + k - 1 <= n; i++) {
			int j = i + k - 1, res0 = 0, res1 = 0;
			for(int l = i + 1; l + 1 < j; l++) {
				res1 = (res1 + (dp[0][i][l] + dp[1][i][l]) * dp[0][l + 1][j] % MOD) % MOD;
			}
			
			if(a[i] == 0 && a[j] == 0) res0 = (res0 + m * (dp[0][i + 1][j - 1] + dp[1][i + 1][j - 1]) % MOD) % MOD;
			else if(a[i] == -a[j] && a[i] > 0) res0 = (res0 + (dp[0][i + 1][j - 1] + dp[1][i + 1][j - 1]) ) % MOD;
			else if( (!a[i] && a[j] < 0) || (!a[j] && a[i] > 0) ) res0 = (res0 + (dp[0][i + 1][j - 1] + dp[1][i + 1][j - 1]) ) % MOD;
			
			dp[0][i][j] = res0;
			dp[1][i][j] = res1;
		}
	}
	/*
	for(int i = 1; i <= n; i++) { 
		for(int j = 1; j <= n; j++) cout << dp[1][i][j] << " "; cout << "dp\n";
	}
	*/
	printf("%lld\n", (dp[0][1][n] + dp[1][1][n]) % MOD);
}

signed main()
{
	//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	int T; scanf("%lld", &T);
	while(T--) Solve();
	return 0;
}

C. Magic

\(a[i]\) 表示为每个地方需要放多少个魔法原料,\(sum[i]\) 表示 \(a[i]\) 的前缀和。

对于每个 \(p[i]\),有 \(a[i-k]+a[i-k+1]+...+a[i+k]=sum[i+k]-sum[i-k-1] \geq p[i]\)

对于每个 \(l, r, b\),有 \(a[l]+a[l+1]+...+a[r]=sum[r]-sum[l-1] \leq b\)

对于每个 \(sum[i]\),有 \(a[i]=sum[i]-sum[i-1] \geq 0\)

对这些不等式转化并进行差分约束求最长路即可求得 \(sum[n]\) 的最小值。

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii pair<int, int>
using namespace std;

const int MAXN = 1e4 + 5;
const int INF = 0x3f3f3f3f;

vector<pii> G[MAXN];
int n, k, q, dis[MAXN], vis[MAXN], cnt[MAXN], ans;
queue<int> Q; 

void Add(int u, int v, int w) {
	G[u].push_back({v, w});
}

bool SPFA(int s) {
	for(int i = 0; i <= n; i++) dis[i] = -INF, vis[i] = cnt[i] = 0;
	dis[s] = 0, vis[s] = 1; Q.push(s);
	while(!Q.empty()) {
		int u = Q.front(); Q.pop();
		vis[u] = 0;
		for(auto i : G[u]) {
			int v = i.fi, w = i.se;
			if(dis[v] < dis[u] + w) {
				dis[v] = dis[u] + w;
				cnt[v] = cnt[u] + 1;
				if(cnt[v] >= n + 1) return 0;
				if(!vis[v]) Q.push(v), vis[v] = 1;
			}
		}
	} 
	return 1;
}

void Solve() {
	cin >> n >> k; k--; n++;
	for(int i = 1; i < n; i++) {
		int p, l, r; cin >> p;
		r = min(n - 1, i + k), l = max(0ll, i - k - 1);
		int u = r, v = l, w = p;
		Add(v + 1, u + 1, w);
	}
	cin >> q;
	for(int i = 1; i <= q; i++) {
		int l, r, b; cin >> l >> r >> b;
		int u = l - 1, v = r, w = -b;
		Add(v + 1, u + 1, w);
	}
	for(int i = 1; i <= n; i++) Add(i - 1, i, 0);
	if(!SPFA(0)) ans = -1;
	else ans = dis[n];
	cout << ans << "\n";
	
	for(int i = 0; i <= n; i++) G[i].clear();
}

signed main()
{
	//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	int T; cin >> T;
	while(T--) Solve();
	return 0;
}

猜结论,输出No即可。

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
#define pir make_pair
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
const int MAXN = 8e5 + 5;

signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	int T; cin >> T;
	while(T--) {
		int n; cin >> n;
		cout << ("No\n");
	}
	return 0;
}

发现 \(m\) 的值较小,考虑用邻接矩阵求解从 \(1\)\(m\) 之间长度为 \(k\) 的方案数。具体做法自行百度“矩阵乘法求路径方案数”。

发现需要找一段连续的区间满足题目要求,考虑用双指针找出一段连续区间。发现我们不好处理除法操作。

考虑如何规避这个除法操作,我们利用对顶栈的技巧。

设一个 \(p\) 作为两个栈的栈顶,\(cur\)\(f[p+1]\)\(f[r]\) 的累乘值,再设 \(g[i]\)\(f[i]\)\(f[p]\) 的累乘值。每次查询 \(f[l]\)\(f[r]\) 时,我们只需要求得 \(g[i] * cur\) 的值即可。

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii pair<int, int>
using namespace std;
const int MAXN = 5000 + 5;
const int INF = 0x3f3f3f3f;

int n, m, k;

struct Matrix {
	int c[21][21];
	
	Matrix operator * (const Matrix& b) const {
		Matrix res;
		for(int i = 1; i <= m; ++i) for(int j = 1; j <= m; ++j) res.c[i][j] = 0;
		for(int k = 1; k <= m; ++k) {
			for(int i = 1; i <= m; ++i) {
				for(int j = 1; j <= m; ++j) {
					res.c[i][j] += c[i][k] * b.c[k][j];
				}
			}
		}
		return res;
	}

	void init(int x) {
		for(int i = 1; i <= m; ++i) for(int j = 1; j <= m; ++j) c[i][j] = 0;
		for(int i = 1; i <= m; i++) c[i][i] = x;
	}
}f[MAXN], g[MAXN];

bool Check(Matrix a, Matrix b) {
	int res = 0;
	for(int i = 1; i <= m; i++) {
		res += a.c[1][i] * b.c[i][m];
		if(res > k) return 0;
	}
	return 1;
}

void Solve() {
	cin >> n >> m >> k;
	for(int i = 1; i <= n; ++i) f[i].init(1);
	for(int i = 1; i <= n; ++i) {
		int l; cin >> l;
		for(int j = 1; j <= l; ++j) {
			int u, v; cin >> u >> v;
			f[i].c[u][v] = 1;
		}
	}
	int maxx = -1; g[0].c[1][m] = INF;
	Matrix cur; cur.init(1);
	for(int l = 0, r = 1, p = 0; r <= n; ++r) {
		cur = cur * f[r];
		while( !Check(g[l], cur) ) {
			l++;
			if(l > p) {
				cur.init(1);
				g[r] = f[r];
				for(int i = r - 1; i > p; i--) g[i] = f[i] * g[i + 1];
				p = r;
			}
		}
		maxx = max(maxx, r - l + 1);
	}
	cout << maxx << "\n";
}

signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	int T; cin >> T;
	while(T--) Solve();
	return 0;
}

F. BIT Subway

签到,根据每次买的票直接对题意模拟即可。

#include<bits/stdc++.h>
#define int long long
#define eps 1e-12
using namespace std;

const int MAXN = 1e5 + 5;

int n;
double a[MAXN], b[MAXN];

double min(double a, double b) {
	return a < b ? a : b;
}

void Solve() {
	scanf("%lld", &n);
	for(int i = 1; i <= n; i++) scanf("%lf", &a[i]), b[i] = a[i];
	double c1 = 0, c2 = 0;
	for(int i = 1; i <= n; i++) {
		if(c1 < 100.0) {
			double minn = min(a[i], 100.0 - c1);
			a[i] -= minn;
			c1 += minn;
		}
		if((100.0 < c1 || abs(c1 - 100.0) < eps) && c1 < 200.0) {
			double minn = min(a[i] * 0.8, (200 - c1) );
			a[i] -= minn / 0.8;
			c1 += minn;
		} 
		if(c1 > 200.0 || abs(c1 - 200.0) < eps) {
			c1 += 0.5 * a[i];
		}
	}
	for(int i = 1; i <= n; i++) {
		if(c2 < 100) c2 += b[i];
		else if(c2 < 200) c2 += 0.8 * b[i];
		else c2 += 0.5 * b[i];
	}
	
	printf("%.3lf %.3lf\n", c1, c2);
}

signed main()
{
	//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	int T; scanf("%lld", &T);
	while(T--) Solve();
	return 0;
}

G. Climb Stairs

如果下一层能直接杀死怪物我们就直接跳到下一层,

如果不能我们应跳到一个最小的位置,使得从这个位置往下走能杀死所有怪物。

具体方法是维护一个 \(need\) 值,表示最多还需多少攻击力杀死怪物。

直接往上维护这个值,当 \(need\) 值小于等于0时即这个位置合法,更新位置即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int MAXN = 1e6 + 5;
const int MOD = 1e9 + 7;

int n, m, a[MAXN], k;

void Solve() {
	cin >> n >> a[0] >> k;
	for(int i = 1; i <= n; i++) cin >> a[i];
	int p = 1, c = k, flag = 1;
	while(p <= n && flag) {
		//cout << p << " p\n";
		if(a[p] <= a[0]) {
			a[0] += a[p];
			c = k; p++;
		} else {
			int i = 1, need = a[p] - a[0], cur = 0;
			while(i < c && p + i - 1 < n) {
				i++;
				need = max(need - a[p + i - 1], a[p + i - 1] - a[0]);
				if(need <= 0) {
					cur = 1;
					for(int j = p; j < p + i; j++) a[0] += a[j];
					p = p + i;
					c = k - i + 1;
					break;
				}
			}
			if(!cur) flag = 0;
		}
	}
	cout << (flag ? "YES" : "NO") << "\n";
}

signed main()
{
	//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	int T; scanf("%lld", &T);
	while(T--) Solve();
	return 0;
}
posted @ 2022-08-03 10:40  Orzjh  阅读(47)  评论(0)    收藏  举报