CSP-S 2021题解
T1:
首先n^2暴力是直接送的,O(n)枚举划分方案,O(n)Check即可,对于Check的方法
将进站设置为i,出站设置为-i,维护一个栈即可
考虑上考虑优化时,考虑的是O(n)Check在所难免,于是考虑优化划分方案数,猜想
函数呈现为单峰函数,然而并不是,只是整体趋势而已(因此可以采用模拟退火)
正解为利用set进行维护,预处理出国内外分配不同的飞机数所能停靠的数量,前缀和
维护,最终O(n)枚举所有方案取max即可,原因在于飞机停靠时刻不同,因此互不影响
时间复杂度O(nlogn),瓶颈在于预处理,类似单调栈每个元素只会进set出set一次。
考场上直接忘记预处理,而采用了错误的思路
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define I int 4 #define LL long long 5 #define C char 6 #define B bool 7 #define V void 8 #define a first 9 #define b second 10 #define P pair<I,I> 11 #define MP make_pair 12 const I N = 1e5 + 3; 13 I n,m1,m2,ans,pre1[N],pre2[N]; 14 set <P> s,t; 15 inline I read () { 16 I x (0), y (1); C z (getchar ()); 17 while (!isdigit(z)) { if (z == '-') y = -1; z = getchar (); } 18 while ( isdigit(z)) x = x * 10 + (z ^ 48), z = getchar (); 19 return x * y; 20 } 21 signed main () { 22 // freopen ("airport.in","r",stdin); 23 // freopen ("airport.out","w",stdout); 24 n = read (), m1 = read (), m2 = read (); 25 for (I i(1);i <= m1; ++ i) { 26 I x (read ()), y (read ()); 27 s.insert (MP (x,y)); 28 } 29 for (I i(1);i <= m2; ++ i) { 30 I x (read ()), y (read ()); 31 t.insert (MP (x,y)); 32 } 33 for (I i(1);i <= n; ++ i) { 34 I sum (0); 35 set <P> :: iterator p1 (s.begin ()),p2; 36 while (p1 != s.end ()) { 37 p2 = s.lower_bound (MP ((*p1).b,(*p1).a)); 38 s.erase (p1), p1 = p2, sum ++ ; 39 } 40 pre1[i] = pre1[i - 1] + sum; 41 } 42 for (I i(1);i <= n; ++ i) { 43 I sum (0); 44 set <P> :: iterator p1 (t.begin ()),p2; 45 while (p1 != t.end ()) { 46 p2 = t.lower_bound (MP ((*p1).b,(*p1).a)); 47 t.erase (p1), p1 = p2, sum ++ ; 48 } 49 pre2[i] = pre2[i - 1] + sum; 50 } 51 for (I i(0);i <= n; ++ i) 52 ans = max (ans,pre1[i] + pre2[n - i]); 53 printf ("%d\n",ans); 54 return 0; 55 }
T2:
暴力枚举字符串+O(n^2)Check有15pts,然而因为Check太麻烦而没打。
正解为区间dp,考场上完全没有想到dp,以为是数学题,做题的套路并不太对
考虑设f[l][r]表示l~r区间合法字符串数量,转移枚举分界点+乘法原理即可,然而
会算重,考虑增加限制进行状态划分,设f[l][r]表示l~r中左右端点括号匹配的方案数
g[l][r]表示l~r左右端点括号不匹配的方案数,转移针对不同字符串进行
O(n^2)预处理出p[l][r]表示l~r是否能完全表示成S形式,则
(S):f[l][r] += p[l][r]
(A):f[l][r] += f[l + 1][r - 1] + g[l + 1][r - 1]
(SA):f[l][r] += sigma(i,1,k)f[l + i + 1][r - 1] + g[l + i + 1][r - 1]
(AS):f[l][r] += sigma(i,1,k)f[l + 1][r - i - 1] + g[l + 1][r - i - 1]
ASB||AB:g[l][r] += (f[l][i] + g[l][i]) * f[j][r] (l < i < j < r)
(其实就是枚举某一段全部为*,因此需要判断该段是否能全部为*) 最后一个转移是钦定后半部分为f,避免算重,手模即可(需要学习这种避免算重的方法)以后模拟赛一定要打暴力
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define I long long 4 #define C char 5 #define B bool 6 #define V void 7 #define LL long long 8 #define memset(name,val,typ,len) memset (name,val,sizeof (typ) * len) 9 const I N = 505, mod = 1e9 + 7; 10 C s[N]; 11 B p[N][N]; 12 I n,k,f[N][N],g[N][N],nxt[N]; 13 inline I read () { 14 I x (0), y (1); C z (getchar ()); 15 while (!isdigit(z)) { if (z == '-') y = -1; z = getchar (); } 16 while ( isdigit(z)) x = x * 10 + (z ^ 48), z = getchar (); 17 return x * y; 18 } 19 inline V Mod1 (I &a,const I &b) { 20 a = a + b > mod ? a + b - mod : a + b; 21 } 22 signed main () { 23 n = read (), k = read (), scanf ("%s",s + 1); 24 for (I i(1);i <= n; ++ i) { 25 B flag (0); 26 for (I j(i);j <= n; ++ j) 27 (s[j] == '*' || s[j] == '?') && !flag ? p[i][j] = 1 : flag = 1; 28 f[i][i + 1]= (s[i] == '(' || s[i] == '?') && (s[i + 1] == ')' || s[i + 1] == '?'); 29 } 30 for (I len(3);len <= n; ++ len) { 31 for (I l(1),r(len);r <= n; ++ l, r = l + len - 1) { 32 if ((s[l] != '(' && s[l] != '?') || (s[r] != ')' && s[r] != '?')) continue; 33 if (len <= k + 2) f[l][r] += p[l + 1][r - 1]; 34 Mod1 (f[l][r],(f[l + 1][r - 1] + g[l + 1][r - 1]) % mod); 35 for (I i(1);i <= min (k,r - l - 2); ++ i) { 36 if (p[l + 1][l + i]) 37 Mod1 (f[l][r],(f[l + i + 1][r - 1] + g[l + i + 1][r - 1]) % mod); 38 if (p[r - i][r - 1]) 39 Mod1 (f[l][r],(f[l + 1][r - i - 1] + g[l + 1][r - i - 1]) % mod); 40 } 41 I pos (0), pre (0); 42 for (I i(l + 1);i <= r - 2; ++ i) { 43 pos = max (pos,i + 1); 44 while ((s[pos] == '*' || s[pos] == '?') && pos <= r - 2) pos ++ ; 45 nxt[i] = min (i + k + 1,pos); 46 } 47 for (I i(l + 2);i <= nxt[l + 1]; ++ i) Mod1 (pre,f[i][r]); 48 Mod1 (g[l][r],(f[l][l + 1] + g[l][l + 1]) * pre % mod); 49 for (I i(l + 2);i <= r - 2; ++ i) { 50 pre = (pre - f[i][r] + mod) % mod; 51 for (I j(nxt[i - 1] + 1);j <= nxt[i]; ++ j) 52 Mod1 (pre,f[j][r]); 53 Mod1 (g[l][r],(f[l][i] + g[l][i]) * pre % mod); 54 } 55 } 56 } 57 printf ("%lld\n",(f[1][n] + g[1][n])% mod); 58 }
T3:
考虑首先暴力比较好想,Dfs遍历即可,考虑剪枝,首先根据要求
在寻找前n个位置时显然不能出现重复的数,因此利用数组记录当前出现过的数即可
然后,因为要构成回文,在遍历后n个数时,可以通过判断添加该数是否能形成回文进行剪枝
然而还是有很多冗余状态,考虑既然要形成回文,那么显然可以双向进行,因此维护4个指针
分别代表前回文拓展到的位置,后回文拓展到的位置,双向搜索。
观察问题发现要求字典序最小,那么可以进行最优性剪枝,先遍历l,后遍历r,得到结果直接
输出并return即可。
综合上述剪枝已经可以通过民间数据,考虑继续优化,发现在Dfs过程中显然4指针会遍历到重复位置
也就是可以记忆化,利用unordered_map记录已经拓展过的位置(结构体中存放4指针),实际上这已经是
正解,然而由于使用map,因此复杂度为O(nlogn)
考虑上述过程显然并不需要Dfs实现,拓展出来,直接先左后右4指针进行拓展,若向左拓展不合法
那么向右拓展一定不合法(根据Dfs很容易理解,每次指针移动仅为1),因此直接遍历即可,时间复杂度O(n)
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define I int 4 #define C char 5 #define B bool 6 #define V void 7 #define LL long long 8 #define memset(name,val,typ,len) memset (name,val,sizeof (typ) * len) 9 const I N = 1e6 + 3; 10 I T,n,a[N]; 11 B flag; 12 C s[N]; 13 inline I read () { 14 I x (0), y (1); C z (getchar ()); 15 while (!isdigit(z)) { if (z == '-') y = -1; z = getchar (); } 16 while ( isdigit(z)) x = x * 10 + (z ^ 48), z = getchar (); 17 return x * y; 18 } 19 V Dfs (I x,I l1,I r1,I l2,I r2) { 20 if (x == n >> 1) { 21 puts (s), flag = 1; 22 return ; 23 } 24 if (a[l1] == a[l2] && l1 < l2) { 25 s[x] = s[n - x - 1] = 'L'; 26 Dfs (x + 1,l1 + 1,r1,l2 - 1,r2); 27 } 28 if (flag) return ; 29 if (a[l1] == a[r2]) { 30 s[x] = 'L', s[n - x - 1] = 'R'; 31 Dfs (x + 1,l1 + 1,r1,l2,r2 + 1); 32 } 33 if (flag) return ; 34 if (a[r1] == a[l2]) { 35 s[x] = 'R', s[n - x - 1] = 'L'; 36 Dfs (x + 1,l1,r1 - 1,l2 - 1,r2); 37 } 38 if (flag) return ; 39 if (a[r1] == a[r2] && r2 < r1) { 40 s[x] = s[n - x - 1] = 'R'; 41 Dfs (x + 1,l1,r1 - 1,l2,r2 + 1); 42 } 43 } 44 signed main () { 45 // freopen ("palin.in","r",stdin); 46 // freopen ("palin.out","w",stdout); 47 T = read (); 48 while (T -- ) { 49 I pos1,pos2; 50 n = read () << 1; flag = 0; s[n] = 0; 51 for (I i(1);i <= n; ++ i) 52 a[i] = read (); 53 for (I i(1);i <= n; ++ i) { 54 if (a[i] == a[1] && i != 1) pos1 = i; 55 if (a[i] == a[n] && i != n) pos2 = i; 56 } 57 s[n - 1] = 'L'; 58 s[0] = 'L', Dfs (1,2, n ,pos1 - 1,pos1 + 1); 59 if ( flag) continue ; 60 s[0] = 'R', Dfs (1,1,n - 1,pos2 - 1,pos2 + 1); 61 if (!flag) puts ("-1"); 62 } 63 }
(纯Dfs,很容易被卡)
T4:
首先一个结论就是最终的局面一定是存在一条分割线使得线两侧颜色不同,且同侧颜色完全相同,
证明考虑反证,若同侧颜色不同那么显然存在更优的方案
于是问题转化为求最小割,网络流Dinic即可。
对于此类两点之间连边无方向性,网络流建图时残量网络权值也设置为z即可
注意慎用memcpy,容易出锅
代码如下:1 #include <bits/stdc++.h> 2 using namespace std; 3 #define I long long 4 #define C char 5 #define B bool 6 #define V void 7 #define LL long long 8 #define memset(name,val,typ,len) memset (name,val,sizeof (typ) * len) 9 const I N = 505; 10 I s,t,d[N*N],now[N*N],maxflow; 11 I T,n,m,cnt,idx[N][N],rec1[N*N],rec2[N*N << 3],size,tmp; 12 I tot,head[N*N],to[N*N << 3],nxt[N*N << 3],wgt[N*N << 3]; 13 inline I read () { 14 I x (0), y (1); C z (getchar ()); 15 while (!isdigit(z)) { if (z == '-') y = -1; z = getchar (); } 16 while ( isdigit(z)) x = x * 10 + (z ^ 48), z = getchar (); 17 return x * y; 18 } 19 inline V found (I x,I y,I z) { 20 to[++tot] = y, nxt[tot] = head[x], wgt[tot] = z, head[x] = tot; 21 to[++tot] = x, nxt[tot] = head[y], wgt[tot] = z, head[y] = tot; 22 } 23 inline V build (I x,I y,I z) { 24 to[++tot] = y, nxt[tot] = head[x], wgt[tot] = z, head[x] = tot; 25 to[++tot] = x, nxt[tot] = head[y], wgt[tot] = 0, head[y] = tot; 26 } 27 B Bfs () { 28 memset (d,0,I,size); 29 for (I i(1);i <= size; ++ i) 30 now[i] = head[i]; 31 queue <I> q; 32 q.push (s), d[s] = 1; 33 while (!q.empty ()) { 34 I x (q.front ()); q.pop (); 35 for (I i(now[x]); i ;i = nxt[i]) 36 if (wgt[i] && !d[to[i]]) { 37 d[to[i]] = d[x] + 1; 38 if (to[i] == t) return true; 39 q.push (to[i]); 40 } 41 } 42 return false; 43 } 44 I Dfs (I x,I flow) { 45 if (x == t) return flow; 46 I rest (flow), tmp; 47 for (I i(now[x]); i ;i = nxt[i]) 48 if (wgt[i] && d[to[i]] == d[x] + 1) { 49 tmp = Dfs (to[i],min (wgt[i],rest)); 50 if (!tmp) d[to[i]] = 0; 51 wgt[i] -= tmp, wgt[i ^ 1] += tmp, rest -= tmp; 52 if (!rest) { now[x] = i; break; } 53 } 54 return flow - rest; 55 } 56 inline I getid (I id) { 57 if (id <= m) return idx[1][id]; 58 if (id <= n + m) return idx[id - m][m]; 59 if (id <= n + m * 2) return idx[n][n + m * 2 + 1 - id]; 60 if (id <= n * 2 + m * 2) return idx[n * 2 + m * 2 + 1 - id][1]; 61 } 62 signed main () { 63 n = read (), m = read (), T = read (), tot = 1; 64 s = ++ cnt; 65 for (I i(1);i <= n; ++ i) 66 for (I j(1);j <= m; ++ j) 67 idx[i][j] = ++ cnt; 68 t = ++ cnt; 69 for (I i(1);i < n; ++ i) 70 for (I j(1);j <= m; ++ j) 71 found (idx[i][j],idx[i + 1][j],read ()); 72 for (I i(1);i <= n; ++ i) 73 for (I j(1);j < m; ++ j) 74 found (idx[i][j],idx[i][j + 1],read ()); 75 size = cnt + n * 2 + m * 2 + 1, tmp = tot; 76 for (I i(1);i <= size; ++ i) rec1[i] = head[i]; 77 for (I i(1);i <= tot; ++ i) rec2[i] = wgt[i]; 78 while (T -- ) { 79 I k (read ()); maxflow = 0; 80 for (I i(1);i <= size; ++ i) head[i] = rec1[i]; 81 for (I i(1);i <= tot; ++ i) wgt[i] = rec2[i]; 82 tot = tmp; 83 for (I i(1);i <= k; ++ i) { 84 I w (read ()), id (read ()); found (cnt + id,getid (id),w); 85 read () ? build (s,cnt + id,INT_MAX) : build (cnt + id,t,INT_MAX); 86 } 87 while (Bfs ()) maxflow += Dfs (s,INT_MAX); 88 printf ("%lld\n",maxflow); 89 } 90 }