【刷题笔记】日照集训 Day7

T2

题意太长了,不放了

题解

状压 DP。
首先注意到,重复 一个数据一定是不优的,所以可以直接 不用管 是否选了重复的,因为一定会有更优的方案把它覆盖掉。
设状态为 \(f_{s_1,s_2,s_3}\),分别表示:

  • 每个程序是否到达 非 OK 的最终状态
  • 每个程序是否满足时间限制
  • 每个程序是否满足空间限制

发现暴力转移是 \(O(8^m)\) 的,考虑优化。
发现当 \(s1 \nsubseteq s2\)\(s1 \nsubseteq s3\) 时这个状态是无意义的(已经到达最终状态,但是没有满足空间或时间限制)。
因此一个点只有 \(\{0,0,0\},\{0,0,1\},\{0,1,0\},\{0,1,1\},\{1,1,1\}\) 五种状态,单次转移 \(5^m\)

实现时用刷表,发现 \(i,j,k\) 转移到的状态 \(s_1,s_2,s_3\),一定满足 \(s_1 \ge i, s_2\ge j, s_3\ge k\),所以从小到大满足 \(i,j,k\) 刷表转移就可以。

code

#include<bits/stdc++.h>
#define N 505
#define M 300
#define pb push_back
#define Fo(a, b) for(auto a : b)
#define fo(a, b, c) for(int b = a; b <= c; b++)
using namespace std;
int n, m, f[M][M][M], c1[N], c2[N], c3[N], ans[N];
struct node{
	int mc, tc;
	char ch;
}a[N][N], b[N];
struct Ans{
	int x, y, z, id;
}pre[M][M][M];
vector<int>v[N], vec;
void init(){
	fo(0, i, (1 << m) - 1) fo(0, j, (1 << m) - 1){
		if((i & j) == i) v[i].pb(j); 
	}
}
bool check(int x, int i){
	fo(1, j, m){
		if(a[i][j].tc > b[j].tc || a[i][j].mc > b[j].mc)
			if(!(x & (1 << (j - 1)))) return 0;
		if(a[i][j].ch != b[j].ch && a[i][j].ch != 'O')
			if(!(x & (1 << (j - 1)))) return 0;
	}
	return 1;
}
void solve(){
	memset(f, 0x3f, sizeof(f)); f[0][0][0] = 0;
	fo(0, i, (1 << m) - 1){
		vec.clear();
		fo(1, j, n) if(check(i, j)){
			vec.pb(j);
			c1[j] = c2[j] = c3[j] = 0;
			fo(1, k, m){
				if(a[j][k].ch == b[k].ch && a[j][k].ch != 'O') c1[j] |= (1 << (k - 1));
				if(a[j][k].tc == b[k].tc) c2[j] |= (1 << (k - 1));
				if(a[j][k].mc == b[k].mc) c3[j] |= (1 << (k - 1));
			}
		}
		Fo(j, v[i]) Fo(k, v[i]) Fo(x, vec){
			int C1 = i | c1[x], C2 = j | c2[x], C3 = k | c3[x];
			if(f[i][j][k] + 1 < f[C1][C2][C3]){
				f[C1][C2][C3] = f[i][j][k] + 1;
				pre[C1][C2][C3] = (Ans){i, j, k, x};
			}
		}
	}
}
int main(){
	//freopen("are1.in", "r", stdin);
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin >> n >> m;
	fo(1, i, n) fo(1, j, m){
		char ch; cin >> a[i][j].ch >> ch >> ch;
		cin >> a[i][j].tc >> ch >> a[i][j].mc; 
	}
	fo(1, j, m) fo(1, i, n){
		b[j].mc = max(b[j].mc, a[i][j].mc), b[j].tc = max(b[j].tc, a[i][j].tc), b[j].ch = a[i][j].ch;
		if(a[i][j].ch != 'O'){
			b[j].ch = a[i][j].ch; break;
		}
	}
	init();
	solve();
	int c = 0;
	//fo(1, i, m) cout << b[i].ch << ' ' << b[i].mc << ' ' << b[i].tc << "\n";
	fo(1, i, m) if(b[i].ch != 'O') c |= (1 << (i - 1));
	cout << f[c][(1 << m) - 1][(1 << m) - 1] << "\n";
	int x = c, y = (1 << m) - 1, z = (1 << m) - 1, tt = f[c][(1 << m) - 1][(1 << m) - 1];
	while(f[x][y][z]){
		ans[tt--] = pre[x][y][z].id;;
		int X = x, Y = y, Z = z;
		x = pre[X][Y][Z].x, y = pre[X][Y][Z].y, z = pre[X][Y][Z].z;
	} 
	tt = f[c][(1 << m) - 1][(1 << m) - 1];
	fo(1, i, tt) cout << ans[i] << ' ';
	return 0;
}

T3

没有题意简化!!!

题解

首先注意到访问的总代价一定是 \(n - 1\),所以要算的是有多少重新赋值的次数。

发现一定是在初始位置 \(x\) 左右走一段连续的区间 \([l,r]\),其中 \(R_{l - 1} > R_x, R_{r + 1} > R_x\),然后将当前的值赋为 \(min(R_{l - 1}, R_{r + 1})\),以此类推。
于是,设当前初始位置为 \(i\)\(A_i\) 是所有 \(R_1, R_2, \dots,R_i\) 前缀最大值的集合,\(B_i\) 是所有 \(R_i, R_2, \dots,R_n\) 后缀最大值的集合,则所需的赋值次数就是 \(f_i= |A_i \cup B_i| - 1\)(让当前值在一个一个往上跳)。

可以开一棵线段树,每一位维护 \(f_i\)
对于 swap 操作,可以先减去 \(R_i,R_{i + 1}\) 的贡献,再加上更改后 \(R_i,R_{i + 1}\) 的贡献。
考虑每一个 \(R_i\) 的贡献:

  • 对于 \(R_i\)\(A\) 集合中的贡献,设第一个 \(\ge R_i\) 的位置为 \(r(r>(i + 1))\),则 \(R_i\)\(A_{i + 1}\dots A_{r - 1}\) 的大小都有 \(1\) 的贡献,所以 \(R_i\)\(f_{i + 1}\dots f_{r - 1}\) 的大小都有 \(1\) 的贡献
  • 对于 \(R_i\)\(B\) 集合中的贡献,设第一个 \(\ge R_i\) 的位置为 \(l(l<(i - 1))\),则 \(R_i\)\(B_{l + 1}\dots B_{i - 1}\) 的大小都有 \(1\) 的贡献,所以 \(R_i\)\(f_{l + 1}\dots f_{i - 1}\) 的大小都有 \(1\) 的贡献,但是 注意 \(R_i\) 的贡献可能在算 \(A\) 的贡献时已经计算过了,所以需要判断 \(A\) 集合中是否有 \(R_i\),发现第一个 \(\ge R_i\) 的数是 \(R_l\),所以只需判断 \(R_l\)\(R_i\) 的关系,若相等,则 \(R_i\) 对区间的贡献为 \(0\),否则为 \(1\)
posted @ 2025-08-16 11:44  GuoSN0410  阅读(7)  评论(0)    收藏  举报