【刷题笔记】日照集训 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\)。

浙公网安备 33010602011771号