2025-11-21 hetao1733837的刷题记录

2025-11-21 hetao1733837的刷题记录

LG14415/LOJ3004 [JOISC 2015] Inheritance

原题链接1:[JOISC 2015] Inheritance

原题链接2:「JOISC 2015 Day 4」Inheritance

分析

呃……居然tag是二分!我们考虑单调性在哪……发现对于一条边,若其不为第$K$次操作的边权(比较小),则不为所有小于$K$操作的删边。考虑怎么$check$,模拟$Kruskal$的过程,通过$K$个并查集进行维护即可。这个思想是真的🐂🍺!

HE一下。

正解

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1005, M = 300005, K = 10005;
struct node{
	int a, b, c, id;
}e[M];
int fa[K][N], sz[K][N], ans[M];
bool cmp(node x, node y){
	return x.c > y.c;
}
int getfa(int k, int x){
	return x == fa[k][x] ? x : fa[k][x] = getfa(k, fa[k][x]);
}
void merge(int k, int x, int y){
	x = getfa(k, x);
	y = getfa(k, y);
	if (x == y)
		return ;
	if (sz[k][x] > sz[k][y])
		swap(x, y);
	fa[k][x] = y;
	sz[k][y] += sz[k][x];
}
int n, m, k;
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m >> k;
	for (int i = 1; i <= m; i++){
		cin >> e[i].a >> e[i].b >> e[i].c;
		e[i].id = i;
	}
	sort(e + 1, e + m + 1, cmp);
	for (int i = 1; i <= n; i++){
		for (int j = 1; j <= k; j++){
			fa[j][i] = i;
			sz[j][i] = 1;
		}
	}
	for (int i = 1; i <= m; i++){
		int u = e[i].a, v = e[i].b, w = e[i].c, id = e[i].id;
		int l = 1, r = k; 
		int pos = 0;
		while (l <= r){
			int mid = (l + r) >> 1;
			if (getfa(mid, u) != getfa(mid, v)){
				pos = mid;
				r = mid - 1;
			}
			else{
				l = mid + 1;
			}
		}
		ans[id] = pos;
		if (pos)
			merge(pos, u, v);
	}
	for (int i = 1; i <= m; i++)
		cout << ans[i] << '\n';
}

LG14412/JOI3001 [JOISC 2015] AAQQZ

原题链接1:

原题链接2:

分析

根本不会,mhh推了两篇题解,粘在这。

AT_joisc2015_h 题解 - Xttttr - 博客园

#6 2023.11.26 - ZSH_ZSH - 博客园

正解

#include <bits/stdc++.h>
using namespace std;
const int N = 3005;
int n, c, a[N], cntl[N], cntr[N], dp[N][N], ans;
int calc(int l, int r, int lim){
    for (int i = 0; i <= c; i++)
        cntl[i] = cntr[i] = 0;
    if (l < 1 || l > n) 
        return 0;
    cntl[a[l]]++;
    int L = l, suml = 1, sumr = 0, maxn = 0, cnt = 0;
    while (L > 1 && a[L - 1] >= a[L]){
        cntl[a[--L]]++;
        suml++;
    }
    int R = a[L];
    L = 0;
    for (int i = r; i <= n; i++){
        cntr[a[i]]++;
        if (a[i] < lim)
            return maxn;
        if (a[i] == lim)
            cnt++;
        else if (cntr[a[i]] > cntl[a[i]]){
            while (R > a[i] && R >= 0){
                suml -= cntl[R];
                R--;
            }
        } 
        else{
            while (L <= c && cntl[L] <= cntr[L]){
                sumr += cntl[L];
                L++;
            }
        }
        int add = (L <= c) ? cntr[L] : 0;
        int len = min(sumr + add, suml);
        if (len + cnt == i - r + 1 && l >= len && l - len >= 0 && i + 1 <= n + 1)
            len += dp[l - len][i + 1];

        maxn = max(maxn, 2 * len + cnt);
    }
    return maxn;
}
void solve(){
    for (int i = 0; i <= n + 1; i++)
        for (int j = 0; j <= n + 1; j++)
            dp[i][j] = 0;
    for (int i = 1; i <= n; i++){
        for (int j = i; j <= n; j++){
            dp[i][j] = 0;
            if (a[i] == a[j])
                dp[i][j] = dp[i - 1][j + 1] + 1;
        }
    }
    for (int i = 1; i <= n; i++)
        ans = max(ans, 2 * dp[i][i] - 1 + calc(i - dp[i][i], i + dp[i][i], 0));
    for (int i = 1; i < n; i++)
        ans = max(ans, 2 * dp[i][i + 1] + calc(i - dp[i][i + 1], i + dp[i][i + 1] + 1, 0));
    for (int i = 1; i <= n; i++){
        int minn = 0;
        for (int j = i + 1; j <= n; j++){
            if (a[j] < a[i]) {
                minn = a[j];
                break;
            }
        }
        ans = max(ans, calc(i, i + 1, minn));
    }
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> c;
    for (int i = 1; i <= n; i++){
        cin >> a[i];
    }
    solve();
    for (int i = 1; i <= n; i++)
        a[i] = c - a[i] + 1;
    reverse(a + 1, a + n + 1);
    solve();
    sort(a + 1, a + n + 1);
    int sum = 0;
    for (int i = 1; i <= n; i++){
        if (a[i] != a[i - 1]){
            ans = max(ans, sum);
            sum = 0;
        }
        sum++;
    }
    ans = max(ans, sum);
    cout << ans;
    return 0;
}

LG14413/LOJ [JOISC 2015] Card Game Is Great Fun

原题链接1:[JOISC 2015] Card Game Is Great Fun - 洛谷

原题链接2:「JOISC 2015 Day 3」Card Game Is Great Fun

分析

14点38分

$DP$是一眼的吧……但是我好像并不会设计状态和转移,稍考一会。

14点39分

想到fqh讲的一个trick,当时是矩阵长宽连边,此题我们难道可以花色点数连边?

那么,我们会建成一张图?这不是废话吗?咦,$V_i\ge 1$似乎可以贪心的$DP$一下?我在写什么?

那,建出图,我们找最长链即可?这不是蓝题吗?

但是,问题在于建图,这样是$O(n^2)$难道要运用另一个限制条件——只取第一或第三个?何意味?

14点50分

何意味啊?居然是高维$DP$!

既然限制了只能取第一或第三,那么,我们就把他计入状态!

即设$f_{i,j}$表示当前序列的第一个元素位置为$i$,第三个元素位置为$j$。但是,选第三个直接转移到$f_{i,j+1}$,但是选第一个没法转移,不知道$i$和$j$之后选哪个。

那么,多加一维$k$表示前三个元素怎么样?也不太行,因为不知道栈顶牌的具体信息。

那么,再加一维吧🤮,设$dp_{i,j,k,l}$表示上一次选择了原序列的第$l$张牌。选第一张即可转移到$f_{j,k,k+1,i}$,选第三张即可转移到$f_{i, j, k+1, k}$。显然,状态是 $O(n^4)$无法满足,/(ㄒoㄒ)/~~蓝题别搞!

发现$k=max(j,l)$,那不就省略一维了吗?

直接跑满!

然后呢,我想说,一些DP题实际上是记忆化搜索的手笔,所以,不会DP时,可以尝试写暴力,然后记忆化一下,说不定有客观的分数!

总结

限制扔进状态,观察数据范围大略估计状态维数,然后拿下!

正解

何意味,少开个long long卡我快20分钟,居然报TLE!

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 505;
int f[N][N][N];
int c[N], a[N], v[N];
int n;
bool check(int x, int y){
	if (!x || !y) //最开始和最结尾随便选 
		return true;
	if (c[x] == c[y] || a[x] == a[y])
		return true;
	return false;
}
int dfs(int i, int j, int k, int l){
	if (f[i][j][l] != -1){
		return f[i][j][l];
	}
	int ans = 0;
	if (i <= n && check(l ,i)){
		ans = max(ans, v[i] + dfs(j, k, k + 1, i));
	}
	if (k <= n && check(l ,k)){
		ans = max(ans, v[k] + dfs(i, j, k + 1, k));
	}
	f[i][j][l] = ans;
	return f[i][j][l];
}
int read(){
    char c = getchar();
    int x = 0, f = 1;
    while (c < '0' || c > '9'){
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9'){
        x = x * 10 + (c - '0');
        c = getchar();
    }
    return x * f;
}
signed main(){
	n = read();
	for (int i = 1; i <= n; i++){
        c[i] = read();
        a[i] = read();
        v[i] = read();
	}
	memset(f, -1, sizeof(f));
	printf("%lld", dfs(1, 2, 3, 0));
}

LG3615/LOJ2734 [JOISC 2016] Toilets

原题链接1:[JOISC 2016] 如厕计划

原题链接2:「JOISC 2016 Day 2」女装大佬

分析

记女生为+1,男生为-1,那么,发现每个男生需要"消耗"一个女生以保证两个厕所都不为空($2N$个人$N$个厕所,空依次就得崩)。那么,转化为后缀和,当后缀和小于$-1$时,就需要调整,然后吃吃吃,就做完了?何意味。

正解

#include <bits/stdc++.h>
using namespace std;
const int M = 100005;
int m;
long long n, ans = -1, k[M];
string s[M];
int main(){
	cin >> n;
	cin >> m;
	for (int i = 1; i <= m; i++){
		cin >> s[i] >> k[i];
		reverse(s[i].begin(), s[i].end());
	}
	long long tmp = 0;
	for (int i = m; i >= 1; i--){
		long long sum = 0, v = 1e9;
		for (int j = 0; j < s[i].length(); j++){
			if (s[i][j] == 'F')
				sum += 1;
			else
				sum += -1;
			v = min(sum, v);
		}
		if (sum >= 0){
			ans = min(ans, tmp + v);
			tmp += sum * k[i];
		} 
		else{
			tmp += sum * (k[i] - 1);
			ans = min(ans, tmp + v);
			tmp += sum;
		}
	}
	ans = -ans - 1LL;
	if (tmp < 0)
		cout << -1;
	else
		cout << ans;
}

posted on 2025-11-22 16:28  hetao1733837  阅读(0)  评论(0)    收藏  举报

导航