2021 CSP-S 题解
- 廊桥分配
- 括号序列
- 回文
- 交通规划
配合👉CSP-S游记 食用更佳哦~
【雷】:只是在民间数据过了,不保证一定正确。仅供参考!!!
【雷】:只是在民间数据过了,不保证一定正确。仅供参考!!!
【雷】:只是在民间数据过了,不保证一定正确。仅供参考!!!
廊桥分配
problem
题目链接
solution
显然暴力就是枚举国内的廊桥数量,然后模拟一遍,O(n2)O(n^2)O(n2)。
正解就是加速这个过程。
设 f1[i]:f_1[i]:f1[i]: 国内有 iii 个廊桥能承载的飞机数量,fi[i]:f_i[i]:fi[i]: 国外的。
最后就是 max{f1[i]+f2[n−i]}\max\Big\{f_1[i]+f_2[n-i]\Big\}max{f1[i]+f2[n−i]}
考虑将 nnn 个廊桥看成 nnn 个桶,对于国内飞机,尽量地往前停,否则就再开一个廊桥停这个飞机,用前缀和就能求得 f1[i]/f2[i]f_1[i]/f_2[i]f1[i]/f2[i]。
可以通过 set\text{set}set 维护,迅速找到每架飞机着陆前空的廊桥。
code
#include <set>
#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 100005
set < pair < int, int > > s;
int f1[maxn], f2[maxn];
int n, m1, m2;void calc( int m, int *f ) {s.clear();for( int i = 1, x, y;i <= m;i ++ ) {scanf( "%d %d", &x, &y );s.insert( { x, y } );}for( int i = 1;i <= m;i ++ ) {auto it = s.begin();while( it != s.end() ) {f[i] ++;int k = it -> second;s.erase( it );it = s.lower_bound( { k + 1, 0 } );}f[i] += f[i - 1];}
}int main() {scanf( "%d %d %d", &n, &m1, &m2 );calc( m1, f1 );calc( m2, f2 );int ans = 0;for( int i = 0;i <= n;i ++ )ans = max( ans, f1[i] + f2[n - i] );printf( "%d\n", ans );return 0;
}
括号序列
problem
题目链接
solution
这道题难就难在 **()**
是不合法的。
为了知道左右的信息,我们选择区间 dpdpdp。
设 g[l,r]:[l,r]g[l,r]:[l,r]g[l,r]:[l,r] 能否全为 ∗*∗
设 f[l,r]:[l,r]f[l,r]:[l,r]f[l,r]:[l,r] 强制 l−rl-rl−r 括号匹配的方案数
设 dp[l,r]:[l,r]dp[l,r]:[l,r]dp[l,r]:[l,r] 为超级完美序列的方案数,不要求 l−rl-rl−r 一定匹配
转移很简单,就是将合法的规则翻译成方程式即可。
(***(...))
枚举左边连续一段 ∗*∗((...)***)
枚举右边连续一段 ∗*∗((...))
直接嵌套一个匹配括号对()**()
l−rl-rl−r 与不同的括号匹配,中间要么为 ∅\empty∅ 要么为连续的 ∗*∗
主要是 dp−fdp-fdp−f 之间相互转移,ggg 是预处理做的。
这就是妥妥的 O(n4)O(n^4)O(n4) 的转移。
正解就是在这个基础上进行了前缀和/后缀和的优化。
code-65’
#include <cstdio>
#define maxn 505
#define int long long
#define mod 1000000007
int n, m;
char s[maxn];
int f[maxn][maxn], dp[maxn][maxn];
bool g[maxn][maxn];signed main() {scanf( "%lld %lld %s", &n, &m, s + 1 );for( int i = 1;i <= n;i ++ ) {if( s[i] == '*' or s[i] == '?' ) g[i][i] = 1;g[i][i - 1] = 1;}for( int len = 2;len <= m;len ++ )for( int i = 1;i + len - 1 <= n;i ++ ) {int j = i + len - 1;if( s[i] == '*' or s[i] == '?' ) g[i][j] |= g[i + 1][j];if( s[j] == '*' or s[j] == '?' ) g[i][j] |= g[i][j - 1];}for( int i = 1;i < n;i ++ )if( s[i] == ')' or s[i] == '*' or s[i + 1] == '(' or s[i + 1] == '*' ) continue;else f[i][i + 1] = dp[i][i + 1] = 1;for( int len = 3;len <= n;len ++ ) for( int i = 1;i + len - 1 <= n;i ++ ) {int j = i + len - 1;if( s[i] == ')' or s[i] == '*' or s[j] == '(' or s[j] == '*' ) continue;f[i][j] = ( f[i][j] + dp[i + 1][j - 1] ) % mod;if( j - 1 - ( i + 1 ) + 1 <= m ) f[i][j] = ( f[i][j] + g[i + 1][j - 1] ) % mod;for( int k = i + 2;k < j - 1;k ++ ) if( k - 1 - ( i + 1 ) + 1 > m ) break;else f[i][j] = ( f[i][j] + g[i + 1][k - 1] * dp[k][j - 1] ) % mod;for( int k = j - 2;k > i + 1;k -- )if( ( j - 1 ) - ( k + 1 ) + 1 > m ) break;else f[i][j] = ( f[i][j] + g[k + 1][j - 1] * dp[i + 1][k] ) % mod;for( int k = i + 1;k < j - 1;k ++ ) for( int t = 0;t <= m;t ++ )if( k + t + 1 >= j ) break;else dp[i][j] = ( dp[i][j] + f[i][k] * dp[k + t + 1][j] * g[k + 1][k + t] ) % mod;dp[i][j] = ( dp[i][j] + f[i][j] ) % mod;}printf( "%lld\n", dp[1][n] );return 0;
}
code-100‘
#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
#define mod 1000000007
#define maxn 505
int f[maxn][maxn], g[maxn][maxn], h[maxn][maxn];
char s[maxn];
int n, m;bool is( int x, char c ) {if( x < 1 or x > n ) return 0;else return s[x] == '?' or s[x] == c;
}signed main() {scanf( "%lld %lld %s", &n, &m, s + 1 );for( int i = 2;i <= n;i ++ ) f[i][i - 1] = 1;for( int l = n;l;l -- )for( int r = l;r <= n;r ++ ) {if( is( l, '(' ) and is( r, ')' ) ) {f[l][r] = f[l + 1][r - 1];for( int i = l + 2;i <= min( l + m + 1, r ) and is( i - 1, '*' );i ++ )f[l][r] = ( f[l][r] + f[i][r - 1] ) % mod;for( int i = r - 2;i >= max( l + 1, r - m - 1 ) and is( i + 1, '*' );i -- )f[l][r] = ( f[l][r] + f[l + 1][i] ) % mod;}h[l][r] = g[l][r] = f[l][r];for( int i = r - 1;i >= r - m and is( i + 1, '*' );i -- ) g[l][r] = ( g[l][r] + h[l][i] ) % mod;for( int i = l;i < r;i ++ ) f[l][r] = ( f[l][r] + g[l][i] * f[i + 1][r] ) % mod;}printf( "%lld\n", f[1][n] );return 0;
}
回文
problem
题目链接
solution
这道题比前面的题可能还要简单一点。
会发现,每次选择最左端或者最右端的数字,连续选择对应在中间的序列也必须是连续的。
i.e.
1....213....3
选最左边的 111,接着选最右边的 333,bbb 序列里面就是 13...
,为了最后的回文,最后几次操作必须是选 333 后立马选 111。
那会不会是 1...123....3
这种呢,123
貌似可以不相邻成为两端。
显然不可能,先选的 13...
之后才选择 222 ,那么回文就必须先选 222,再选 31
,而 222 都被 111 和 333 堵死了。
所以这就是一个贪心模拟的过程。
优先从左边选择开始模拟。
code
#include <cstdio>
#define maxn 1000005
int T, n, flag;
int a[maxn], ans[maxn];
int pos[2][maxn];void print() {int l = 1, r = n << 1;for( int i = 1;i <= ( n << 1 );i ++ )if( ans[i] == l ) printf( "L" ), l ++;else printf( "R" ), r --;printf( "\n" );
}void solve( int l, int r, int L, int R ) {for( int i = 2;i <= n;i ++ ) {if( l < L ) {if( pos[1][a[l]] == L - 1 ) { ans[i] = l ++, ans[(n << 1 | 1) - i] = -- L; continue; }if( pos[1][a[l]] == R + 1 ) { ans[i] = l ++, ans[(n << 1 | 1) - i] = ++ R; continue; }}if( r > R ) {if( pos[0][a[r]] == L - 1 ) { ans[i] = r --, ans[(n << 1 | 1) - i] = -- L; continue; }if( pos[0][a[r]] == R + 1 ) { ans[i] = r --, ans[(n << 1 | 1) - i] = ++ R; continue; }}flag = 0;return;}
}int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d", &n );for( int i = 1;i <= n;i ++ ) pos[0][i] = pos[1][i] = 0;for( int i = 1;i <= ( n << 1 );i ++ ) {scanf( "%d", &a[i] );if( ! pos[0][a[i]] ) pos[0][a[i]] = i;else pos[1][a[i]] = i;}flag = 1;ans[1] = 1, ans[n << 1] = pos[1][a[1]];solve( 2, n << 1, pos[1][a[1]], pos[1][a[1]] );if( flag ) { print(); continue; }flag = 1;ans[1] = n << 1, ans[n << 1] = pos[0][a[n << 1]];solve( 1, (n << 1) - 1, pos[0][a[n << 1]], pos[0][a[n << 1]] );if( flag ) { print(); continue; }printf( "-1\n" );}return 0;
}
交通规划
problem
题目链接
solution
只能黑白染色,然后端点颜色不同的边就会计算贡献。
如果想到了,就是非常明显的网络流。
这种平面图般的网络流是很经典的有最短路优化——BZOJ上有一题网络流的经典“狼抓兔子”。
就算不会最短路优化也是可以暴力上网络流硬跑前 60′60'60′ 的部分分。
code-60‘
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define inf 1e18
#define maxn 505
#define Maxn 260000
#define maxm 1200000
#define int long long
queue < int > q;
struct node { int to, nxt, flow; }E[maxm];
int n, m, T, cnt, N, s, t, k;
int head[Maxn], cur[Maxn], dis[Maxn];
int a[maxn][maxn], b[maxn][maxn];void addedge( int u, int v, int flow ) {E[cnt] = { v, head[u], flow };head[u] = cnt ++;E[cnt] = { u, head[v], flow };head[v] = cnt ++;
}bool bfs() {memset( dis, 0, sizeof( dis ) );dis[s] = 1, q.push( s );while( ! q.empty() ) {int u = q.front(); q.pop();for( int i = cur[u] = head[u];~ i;i = E[i].nxt ) {int v = E[i].to;if( ! dis[v] and E[i].flow ) {dis[v] = dis[u] + 1;q.push( v );}}}return dis[t];
}int dfs( int u, int cap ) {if( u == t or ! cap ) return cap;int flow = 0;for( int i = cur[u];~ i;i = E[i].nxt ) {cur[u] = i; int v = E[i].to;if( dis[v] == dis[u] + 1 ) {int w = dfs( v, min( cap, E[i].flow ) );if( ! w ) continue;E[i ^ 1].flow += w;E[i].flow -= w;flow += w;cap -= w;if( ! cap ) break;}}return flow;
}int dinic() {int ans = 0;while( bfs() ) ans += dfs( s, inf );return ans;
}int id( int i, int j ) { return ( i - 1 ) * m + j; }signed main() {scanf( "%lld %lld %lld", &n, &m, &T );for( int i = 1;i < n;i ++ ) for( int j = 1;j <= m;j ++ ) scanf( "%lld", &a[i][j] );for( int i = 1;i <= n;i ++ ) for( int j = 1;j < m;j ++ ) scanf( "%lld", &b[i][j] );while( T -- ) {cnt = 0, memset( head, -1, sizeof( head ) );scanf( "%lld", &k );N = n * m, s = N + k + 1, t = s + 1;for( int i = 1;i < n;i ++ ) for( int j = 1;j <= m;j ++ ) addedge( id(i, j), id(i + 1, j), a[i][j] );for( int i = 1;i <= n;i ++ ) for( int j = 1;j < m;j ++ ) addedge( id(i, j), id(i, j + 1), b[i][j] );for( int i = 1;i <= N;i ++ ) addedge( s, i, 0 ), addedge( i, t, 0 );for( int i = 1, w, x, c;i <= k;i ++ ) {scanf( "%lld %lld %lld", &w, &x, &c );++ N; int pos;if( x <= m ) pos = id( 1, x );else if( x <= n + m ) pos = id( x - m, m );else if( x <= n + m * 2 ) pos = id(n, 2 * m + n - x + 1);else pos = id( 2 * ( m + n ) - x + 1, 1 );addedge( N, pos, w );if( c ) addedge( s, N, w );else addedge( N, t, w );}printf( "%lld\n", dinic() );}return 0;
}