2025“钉耙编程”中国大学生算法设计春季联赛(8) Problems


好久没打过杭电了,想着这周周五下午的课已经结课了,就来打一打。开局写前两道题结果这两题数据全是错的,害的我差点怀疑人生。

1001. 拼图游戏

题意:一个\(n\times m\)的矩阵,只包含\([1, k]\)的数。求有多少\((x, y)\)满足\((1, 1)\)\((x, y)\)这个子矩阵包含\(1\)\(k\)的数至少一次。

没什么思路。直接用bieset草过去。
用bitset记录\(g[i][j]\)表示第\(i\)行前\(j\)个的元素状态。
那么我们可以枚举\(y\),然后看\(x\)需要到哪里才能满足条件。

点击查看代码
const int N = 1010;

std::bitset<2000> g[N][N];

void solve() {
    int n, m, k;
    std::cin >> n >> m >> k;
    std::vector a(n + 1, std::vector<int>(m + 1));
    for (int i = 1; i <= n; ++ i) {
    	for (int j = 1; j <= m; ++ j) {
    		std::cin >> a[i][j];
    		-- a[i][j];
    	}
    }

    for (int i = 1; i <= n; ++ i) {
    	for (int j = 1; j <= m; ++ j) {
	    	g[i][j].reset();
    		g[i][j] = g[i][j - 1];
    		g[i][j][a[i][j]] = 1;
    	}
    }

    int ans = 0;
	std::bitset<2000> s;
    for (int j = 1; j <= m; ++ j) {
    	s.reset();
    	for (int i = 1; i <= n; ++ i) {
    		s |= g[i][j];
    		if (s.count() == k) {
    			ans += n - i + 1;
    			break;
    		}
    	}
    }

    std::cout << ans << "\n";
}

1002. 缓存系统

题意:有\(n\times m\)个题,第\((i, j)\)个题的数据如果放到内存需要\(s[i][j]\)的容量,否则需要\(a[i][j]\)的代价。对于第\(i\)行,你必须选择一个前缀(可以非空)放到内存。你的内存只有\(x\)的容量,求最小代价。

把每一行分成\(m + 1\)个物品,第\(i\in [0, m]\)个物品重量为前\(i\)个题的内存和,价值为后面所有题的代价和。那么就变成了一个背包问题。

点击查看代码
void solve() {
    int n, m, k;
    std::cin >> n >> m >> k;
    std::vector s(n, std::vector<int>(m));
    std::vector a(n, std::vector<i64>(m));
    for (int i = 0; i < n; ++ i) {
    	for (int j = 0; j < m; ++ j) {
    		std::cin >> s[i][j] >> a[i][j];
    	}
    }

    std::vector<std::vector<std::pair<int, i64>>> goods(n);
    for (int i = 0; i < n; ++ i) {
    	i64 sum = std::accumulate(a[i].begin(), a[i].end(), 0ll);
    	int pre = 0;
    	goods[i].emplace_back(0, sum);
    	for (int j = 0; j < m; ++ j) {
    		pre += s[i][j];
    		sum -= a[i][j];
    		if (pre > k) {
    			break;
    		}
    		goods[i].emplace_back(pre, sum);
    	}
    }

    const i64 inf = 1e18;
    std::vector<i64> f(k + 1);
    for (int i = 0; i < n; ++ i) {
	    std::vector<i64> g(k + 1, inf);
    	for (auto & [w, v] : goods[i]) {
    		for (int j = w; j <= k; ++ j) {
    			g[j] = std::min(g[j], f[j - w] + v);
    		}
    	}

    	f = g;
    }

    std::cout << f[k] << "\n";
}

1003. 独到寒山顶

题意:一个矩阵,有些地方不能走,你需要从第一行选一个位置出发,求到最后一行任意一个位置最少经过的格子。

多源\(bfs\)。把第一行的点都入队。然后\(bfs\)即可。答案就是最后一行的最小距离。

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector g(n, std::vector<int>(m));
    for (int i = 0; i < n; ++ i) {
    	int k;
    	std::cin >> k;
    	while (k -- ) {
    		int j;
    		std::cin >> j;
    		-- j;
    		g[i][j] = 1;
    	}
    }

    const int inf = 1e9;
    std::vector d(n, std::vector<int>(m, inf));
    std::queue<std::pair<int, int>> q;
    for (int i = 0; i < n; ++ i) {
    	if (g[i][0] == 0) {
    		q.emplace(i, 0);
    		d[i][0] = 1;
    	}
    }

    const int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
    while (q.size()) {
    	auto [x, y] = q.front(); q.pop();
    	for (int i = 0; i < 4; ++ i) {
    		int nx = x + dx[i], ny = y + dy[i];
    		if (nx < 0 || nx >= n || ny < 0 || ny >= m || g[nx][ny]) {
    			continue;
    		}

    		if (d[nx][ny] == inf) {
    			d[nx][ny] = d[x][y] + 1;
    			q.emplace(nx, ny);
    		}
    	}
    }

    int ans = inf;
    for (int i = 0; i < n; ++ i) {
    	ans = std::min(ans, d[i][m - 1]);
    }
    std::cout << ans << "\n";
}

1005. 咒语附魔

题意:给你两个二进制串\(s, t\),从第二个串里选一个和第一个串长度相等的子串,要求异或最大。求这个最大串的1的个数。保证数据随机。

切入点为数据随机。
想要异或最大,我们应该从高位开始匹配,每次尽量和这一位不一样的位置匹配,那么我们可以记录\(t\)中所有可以匹配的位置,如果一个位置和当前匹配是异或为1的可以留下。如果没有一个异或为1的则全部保留。
因为是随机数据,则\(0\)\(1\)的个数差不多是占\(\frac{1}{2}\),那么每次保留的位置会减半。

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::string s, t;
    std::cin >> s >> t;
    std::vector<int> p;
    for (int i = 0; i + n - 1 < m; ++ i) {
    	p.push_back(i);
    }

    int ans = 0;
    for (int i = 0; i < n; ++ i) {
    	std::vector<int> np;
    	for (auto & j : p) {
    		if (s[i] != t[j]) {
    			np.push_back(j + 1);
    		}
    	}

    	if (np.size()) {
	    	p = np;
    		++ ans;
    	} else {
    		for (auto & j : p) {
    			++ j;
    		}
    	}
    }

    std::cout << ans << "\n";
}

1007. 架子鼓

题意:给你两组分数,求它们的前缀和有几个相同的。

直接实现一个分数类。然后用\(map\)存第一组的前缀和,接下来枚举第二组前缀和看有没有在第一组出现过。

点击查看代码
struct Node {
	i64 a, b;
};

bool operator == (const Node & a, const Node & b) {
	return a.a == b.a && a.b == b.b;
}

i64 lcm(i64 n, i64 m) {
	return n / std::gcd(n, m) * m;
}

Node operator + (const Node & a, const Node & b) {
	if (a.a == 0) {
		return b;
	}
	if (b.a == 0) {
		return a;
	}
	i64 m = lcm(a.b, b.b);
	i64 n = a.a * (m / a.b) + b.a * (m / b.b);
	i64 d = std::gcd(n, m);
	n /= d, m /= d;
	return Node{n, m};
}

void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<Node> a(n), b(m);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i].a >> a[i].b;
    }

    for (int i = 0; i < m; ++ i) {
    	std::cin >> b[i].a >> b[i].b;
    }

    std::map<std::pair<i64, i64>, int> mp;
    Node pre{0, 1};
    for (int i = 0; i + 1 < n; ++ i) {
    	pre = pre + a[i];
    	++ mp[{pre.a, pre.b}];
    }

    int ans = 1;
    pre = {0, 1};
    for (int i = 0; i + 1 < m; ++ i) {
    	pre = pre + b[i];
    	ans += mp[{pre.a, pre.b}];
    }
    std::cout << ans << "\n";
}

1009. 能量网络

题意:\(n\)个点,\(m\)条边,每个点有三个坐标,一条边的三个坐标等于它连接的\((u, v)\)对应三个坐标的和。两个边的距离为\(\min(|x_i - x_j|, |y_i - y_j|, |z_i - z_j|)\)把边当成点求最小生成树。

分别按照三个坐标排序,然后个相邻两个边连边。做\(kruskal\)
因为一条边一定是选距离它某个坐标最近的一条边。

点击查看代码
struct DSU {
	std::vector<int> fa, cnt;
	DSU(int _n) {
		init(_n);
	}

	void init(int _n) {
		fa.assign(_n, 0);
		cnt.assign(_n, 1);
		std::iota(fa.begin(), fa.end(), 0);
	}

	int find(int x) {
		return x == fa[x] ? x : fa[x] = find(fa[x]);
	}

	bool merge(int x, int y) {
		x = find(x), y = find(y);
		if (x == y) {
			return false;
		}

		fa[y] = x;
		cnt[x] += cnt[y];
		return true;
	}

	bool same(int x, int y) {
		return find(x) == find(y);
	}

	int size(int x) {
		return cnt[find(x)];
	}
};

void solve() {
    int n, m;
    std::cin >> n >> m;
    using A = std::array<int, 3>;
    std::vector<A> a(n);
    for (int i = 0; i < n; ++ i) {
    	int x, y, z;
    	std::cin >> x >> y >> z;
    	a[i] = {x, y, z};
    }

    std::vector<std::array<int, 4>> b(m);
    for (int i = 0; i < m; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	-- u, -- v;
    	b[i] = {a[u][0] + a[v][0], a[u][1] + a[v][1], a[u][2] + a[v][2], i};
    }

    std::vector<A> edges;
    std::sort(b.begin(), b.end());
    for (int i = 0; i + 1 < m; ++ i) {
    	edges.push_back(A{b[i + 1][0] - b[i][0], b[i][3], b[i + 1][3]});
    }

    std::sort(b.begin(), b.end(), [&](std::array<int, 4> & a, std::array<int, 4> & b) {
    	return a[1] < b[1];
    });

    for (int i = 0; i + 1 < m; ++ i) {
    	edges.push_back(A{b[i + 1][1] - b[i][1], b[i][3], b[i + 1][3]});
    }

    std::sort(b.begin(), b.end(), [&](std::array<int, 4> & a, std::array<int, 4> & b) {
    	return a[2] < b[2];
    });

    for (int i = 0; i + 1 < m; ++ i) {
    	edges.push_back(A{b[i + 1][2] - b[i][2], b[i][3], b[i + 1][3]});
    }

    std::sort(edges.begin(), edges.end());
    i64 ans = 0;
    DSU d(m);
    for (auto & [w, u, v] : edges) {
    	if (d.same(u, v)) {
    		continue;
    	}

    	ans += w;
    	d.merge(u, v);
    }

    std::cout << ans << "\n";
}

1010. 字符串哈希

题意:题目给了两个\(hash\)函数:

求有多少长度小于等于\(k\)的字符串满足\(B(s)^3 \times c + B(s)^2 \times d + B(s) \times e + f = A(s)\)

观察到\(k\)十分小,我们可以通过\(dp\)看长度小于等于\(k\)的串里\([0, 10007)\)的这些值是否能出现。可以通过\(f[i][j]\)推出\(f[i + 1][(j + k \times 10^i) \% 10007], k\in [1, 26]\),类似一个背包。
然后我们可以发现\(A(s)\)其实就是一个\(27\)进制数,于是可以求\(s\in [1, 10007]\)\(27\)进制表示,它里面不能除了高位连续的1外中间出现一个1。因为\(ord(s_i)\)的值是\([1, 26]\)之间,不可能出现\(0\)
然后把这些合法的状态推出它的\(B()\)的值,判断是不是和\(s\)相等。

点击查看代码
const int N = 10007;

void solve() {
    i64 k, c, d, e, f;
    std::cin >> k >> c >> d >> e >> f;
    std::vector<i64> p10(k + 1), p27(k + 1);
    p10[0] = p27[0] = 1;
    for (int i = 1; i <= k; ++ i) {
    	p10[i] = p10[i - 1] * 10;
    	p27[i] = p27[i - 1] * 27;
    }

    std::vector<i64> dp(N);
    dp[0] = 1;
    for (int i = 0; i < k; ++ i) {
        auto ndp = dp;
    	for (int j = 0; j < N; ++ j) {
    		if (!dp[j]) {
    			continue;
    		}
    		for (int k = 1; k <= 26; ++ k) {
    			ndp[(j + k * p10[i] % N) % N] |= dp[j];
    		}
    	}

    	dp = ndp;
    }

    i64 ans = 0;
    for (int i = 1; i < N; ++ i) {
        if (!dp[i]) {
            continue;
        }

    	i64 s = (i64)i * i * i * c + (i64)i * i * d + (i64)i * e + f;
    	if (s >= p27[k]) {
    		continue;
    	}

    	int cnt = 0;
    	std::vector<int> a(k);
    	for (int j = 0; j < k; ++ j) {
    		i64 p = s % p27[j + 1] / p27[j];
    		a[j] = p;
    	}

    	bool flag = true;
    	for (int i = 1; i < k; ++ i) {
    		if (a[i] != 0 && a[i - 1] == 0) {
    			flag = false;
    			break;
    		}
    	}

    	if (flag && a[0] != 0) {
            int val = 0;
            for (int i = 0; i < k; ++ i) {
                val = (val + (i64)a[i] * p10[i] % N) % N;
            }
            if (val == i) {
                ++ ans;
            }
    	}
    }
    std::cout << ans << "\n";
}
posted @ 2025-04-25 22:46  maburb  阅读(240)  评论(0)    收藏  举报