C. 重测队列
有 n 个程序使用同一个测试点顺序。程序会依次运行测试点,遇到第一个拒绝结果后停止;每个程序在每个测试点上的运行时间与结果均已知。可以任意重排 m 个测试点,求评测全部程序的最小总时间。
数据规模:1≤n≤2×104,1≤m≤20,1≤di,j≤109。
学长说这题要贪心,对于每行统计正贡献,对每列统计负贡献。
但我写完 I 回来看这题的时候,注意到 m≤20,结合题目考察,我的直觉是状态压缩,而且很适合直接转移。
为什么感觉每一场牛客都有状压DP 啊……
于是记 f[S] 表示已经完成集合 S 中所有测试点时,能够得到的最小总耗时,由于状态与内部顺序无关,所以可以只保留到达同一集合的最小耗时。
接着考虑转移,若接下来运行测试点 j∈/S,真正会运行到 j 的程序,是那些在集合 S 中没有拒绝任何测试点的程序:
f[S∪{j}]=min(f[S∪{j}],f[S]+i:Ri∩S=∅∑di,j)
其中 Ri 表示程序 i 会拒绝的测试点集合。
难点在这个转移,我最开始直接对所有的子集作枚举,但这个复杂度是 O(3m),是无法通过的。
1 2 3
for( int sub = cur; sub; sub = ( sub - 1 ) & cur ) { if( __builtin_popcount( cur ) - __builtin_popcount( sub ) != 1 ) continue; }
但是我发现每次转移只新增一个测试点,所以直接枚举 cur 中最后加入的那个测试点不就行了吗,这样的复杂度为 O(m⋅2m)。
1 2 3 4 5 6 7 8 9 10 11 12
while( rem ) { int bit = rem & -rem; int j = __builtin_ctz( bit ); f[mask | bit] = min( f[mask | bit], f[mask] + h[j][all ^ mask] ); rem ^= bit; } // lowbit 枚举二进制的每一位
for( int x = rem; x; x &= x - 1 ) { int bit = x & -x; int j = __builtin_ctz( bit ); f[mask | bit] = min( f[mask | bit], f[mask] + h[j][all ^ mask] ); } // 这还有一种 for 的等价写法
但是即便这样枚举还是会 TLE,因为我最开始的写法是:
1 2 3 4 5 6 7 8
set<int> used; for( int j = 0; j < m; ++ j ) { if( sub & ( 1 << j ) ) { for( int i = 0; i < n; ++ i ) { if( s[i][j] == 'R' ) used.insert( i ); } } }
int a[maxn][maxm]; ll h[maxm][(1 << 20)]; ll f[(1 << 20)];
voidsolve( ){ int n = read( ), m = read( ); int M = 1 << m; int all = M - 1;
for( int i = 0; i < n; ++ i ) { for( int j = 0; j < m; ++ j ) { a[i][j] = read( ); } int Rmask = 0; for( int j = 0; j < m; ++ j ) { char ch = getchar( ); if( ch == 'R' ) Rmask |= (1 << j); } for( int j = 0; j < m; ++ j ) { h[j][Rmask] += a[i][j]; } }
// SOS DP for( int j = 0; j < m; ++ j ) { for( int bit = 0; bit < m; ++ bit ) { for( int mask = 0; mask < M; ++ mask ) { if( mask & ( 1 << bit ) ) { h[j][mask] += h[j][mask ^ ( 1 << bit )]; } } } }
for( int i = 0; i < M; ++ i ) f[i] = inf; f[0] = 0;
for( int mask = 0; mask < M; ++ mask ) { int rem = all ^ mask; for( int x = rem; x; x &= x - 1 ) { int bit = x & -x; int j = __builtin_ctz( bit ); f[mask | bit] = min( f[mask | bit], f[mask] + h[j][all ^ mask] ); } } cout << f[all] << '\n'; }
vector<int> ans; if( n & 1 ) { ans.push_back( 2 ); int k = n / 2 + 2; int a = k - 1, b = k + 1; for( int i = 1; i <= n / 2 - 1; ++ i ) { ans.push_back( a ); ans.push_back( b ); a --, b ++; } ans.push_back( 1 ); ans.push_back( k ); } else { int a = n / 2 + 1, b = n / 2; for( int i = 1; i <= n / 2; ++ i ) { ans.push_back( a ); ans.push_back( b ); a ++, b --; } }
bool flag = 0; for( int x : ans ) { if( x == 1 ) flag = 1; if( flag ) cout << x << ' '; } for( int x : ans ) { if( x == 1 ) break; cout << x << ' '; } cout << '\n';
structSegTree { /* ======================================== */ structNode { int l, r; int mx[MAXL + 1]; } tr[maxn << 2];
voidpush_up( int u, int len ){ tr[u].mx[len] = max( tr[ls].mx[len], tr[rs].mx[len] ); }
voidbuild( int u, int l, int r ){ tr[u].l = l, tr[u].r = r; fill( tr[u].mx, tr[u].mx + MAXL + 1, 0 ); if( l == r ) return; int mid = ( l + r ) >> 1; build( ls, l, mid ); build( rs, mid + 1, r ); }
voidmodify( int u, int len, int pos, int val ){ if( tr[u].l == tr[u].r ) { tr[u].mx[len] = max( tr[u].mx[len], val ); return; } int mid = ( tr[u].l + tr[u].r ) >> 1; if( pos <= mid ) modify( ls, len, pos, val ); elsemodify( rs, len, pos, val ); push_up( u, len ); }
intquery( int u, int len, int l, int r ){ if( l > r ) return0; if( l <= tr[u].l && tr[u].r <= r ) return tr[u].mx[len]; int mid = ( tr[u].l + tr[u].r ) >> 1; int res = 0; if( l <= mid ) res = max( res, query( ls, len, l, r ) ); if( mid < r ) res = max( res, query( rs, len, l, r ) ); return res; } intask( int len ){ return tr[1].mx[len]; } /* ======================================== */ } seg;
ll a[maxn]; int pos[maxn], ql[maxn], qr[maxn]; int ans[maxn];
voidsolve( ){ int n = read( ), q = read( );
disc.clear( ); for( int i = 1; i <= n; ++ i ){ a[i] = read( ); disc.add( a[i] ); } disc.init( );
for( int i = 1; i <= n; ++ i ) { pos[i] = disc.low_idx( a[i] ); ql[i] = disc.low_idx( ( a[i] + 2 ) / 3 ); qr[i] = disc.high_idx( a[i] / 2 ); }
vector<Query> qs( q ); for( int i = 0; i < q; ++ i ) { qs[i].l = read( ), qs[i].r = read( ); qs[i].idx = i; }
sort( qs.begin( ), qs.end( ) );
seg.build( 1, 1, disc.size( ) );
int cur = 0; int mxlen = 0;
for( auto [l, r, idx] : qs ) { while( cur < r ) { ++ cur; int lstlen = mxlen;
for( int len = 1; len <= lstlen && len < MAXL; ++ len ) { int st = seg.query( 1, len, ql[cur], qr[cur] ); if( st == 0 ) break; seg.modify( 1, len + 1, pos[cur], st ); mxlen = max( mxlen, len + 1 ); }
seg.modify( 1, 1, pos[cur], cur ); mxlen = max( mxlen, 1 ); } for( int len = mxlen; len >= 1; -- len ) { if( seg.ask( len ) >= l ) { ans[idx] = len; break; } } }
for( int i = 0; i < q; ++ i ) { cout << ans[i] << '\n'; } }