GZOI2019&GXOI2019 滚粗记
day2T1看错题意导致比别人少了20~100分。
爆零,退役,GG。
题目,数据及标程在码学堂上面都有。
Day1:
T1:
首先50分的暴力很容易,枚举左上角然后$n^2$的枚举子矩阵即可。
二进制的题一般考虑按位来处理,我们把每个数的第k位提出来,得到了一个01矩阵。
在按位与的过程中,一旦碰见0,以后怎么与这一位都只能是0,所以所有全1子矩阵的个数即为对答案的贡献。
按位或也是同理,我们只要算出包含0的所有子矩阵个数即可,可以通过算出全0子矩阵的个数,然后用总子矩阵个数减去即可。
利用单调栈进行计算,总的时间复杂度是$O(log(2^{31} - 1)n^2)$
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 #define int long long 5 const int MOD = 1000000007; 6 const int MAXN = 1010; 7 8 int a[MAXN][MAXN], bit[MAXN][MAXN]; 9 int sum[MAXN][MAXN], up[MAXN], down[MAXN]; 10 int top, sta[MAXN]; 11 int n, ans1, ans2; 12 13 int count1() 14 { 15 int num = 0; 16 for(int i = 1; i <= n; ++i) 17 for(int j = 1; j <= n; ++j) 18 { 19 if(bit[i][j]) sum[i][j] = sum[i][j - 1] + 1; 20 else sum[i][j] = 0; 21 } 22 for(int j = 1; j <= n; ++j) 23 { 24 top = 0; 25 for(int i = 1; i <= n; ++i) 26 { 27 if(sum[i][j]) 28 { 29 up[i] = 1; 30 while(top && sum[i][j] <= sum[sta[top]][j]) up[i] += up[sta[top--]]; 31 sta[++top] = i; 32 } 33 else top = 0, up[i] = 0; 34 } 35 top = 0; 36 for(int i = n; i; --i) 37 { 38 if(sum[i][j]) 39 { 40 down[i] = 1; 41 while(top && sum[i][j] < sum[sta[top]][j]) down[i] += down[sta[top--]]; 42 sta[++top] = i; 43 } 44 else top = 0, up[i] = 0; 45 num += up[i] * down[i] * sum[i][j]; 46 } 47 } 48 return num; 49 } 50 51 int count2() 52 { 53 int num = 0; 54 for(register int i = 1; i <= n; ++i) 55 for(register int j = 1; j <= n; ++j) 56 { 57 if(!bit[i][j]) sum[i][j] = sum[i][j - 1] + 1; 58 else sum[i][j] = 0; 59 } 60 for(register int j = 1; j <= n; ++j) 61 { 62 top = 0; 63 for(register int i = 1; i <= n; ++i) 64 { 65 if(sum[i][j]) 66 { 67 up[i] = 1; 68 while(top && sum[i][j] <= sum[sta[top]][j]) up[i] += up[sta[top--]]; 69 sta[++top] = i; 70 } 71 else top = 0, up[i] = 0; 72 } 73 top = 0; 74 for(register int i = n; i; --i) 75 { 76 if(sum[i][j]) 77 { 78 down[i] = 1; 79 while(top && sum[i][j] < sum[sta[top]][j]) down[i] += down[sta[top--]]; 80 sta[++top] = i; 81 } 82 else top = 0, up[i] = 0; 83 num += up[i] * down[i] * sum[i][j]; 84 } 85 } 86 return (n * n * (n + 1) * (n + 1) / 4ll - num) % MOD; 87 } 88 89 signed main() 90 { 91 scanf("%lld", &n); 92 for(int i = 1; i <= n; ++i) 93 for(int j = 1; j <= n; ++j) scanf("%lld", &a[i][j]); 94 for(int k = 0; k <= 30; ++k) 95 { 96 for(int i = 1; i <= n; ++i) 97 for(int j = 1; j <= n; ++j) bit[i][j] = ((a[i][j] >> k) & 1); 98 ans1 = (ans1 + (1ll << k) % MOD * count1() % MOD) % MOD; 99 ans2 = (ans2 + (1ll << k) % MOD * count2() % MOD) % MOD; 100 } 101 printf("%lld %lld\n", ans1, ans2); 102 return 0; 103 }
T2:
感觉有点像那个什么斗地主加强版。但是省选出这种题真的好吗?
七对子的情况考虑DP,设$dp[i][j]$表示前$i$种牌,选了$j$个对子的最大分值。
国士无双的情况直接枚举哪种牌选两张即可。
对于其他情况,我们设$f[i][j][k][l][m][n]$表示:前$i$种牌,选了$j$组面子/杠子,$k$组雀头,其中第$i-2~i$种牌分别选了$l,m,n$张时,前$i-3$种牌可以获得的最大价值。
$dp$的转移可以用刷表法,这样要方便许多。
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 #define int long long 5 const bool shunzi[35] = {0,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0}; 6 const int wushuang[13] = {1,9,10,18,19,27,28,29,30,31,32,33,34}; 7 8 int c[5][5], a[35], is[35]; 9 int f[35][5][2][5][5][5], seven[35][8]; 10 11 int getid(char str[]) 12 { 13 if(str[1] == 'm') return str[0] - 48; 14 if(str[1] == 'p') return str[0] - 48 + 9; 15 if(str[1] == 's') return str[0] - 48 + 18; 16 if(str[0] == 'E') return 28; 17 if(str[0] == 'S') return 29; 18 if(str[0] == 'W') return 30; 19 if(str[0] == 'N') return 31; 20 if(str[0] == 'Z') return 32; 21 if(str[0] == 'B') return 33; 22 if(str[0] == 'F') return 34; 23 return 0; 24 } 25 26 int bao(int idx, int cnt) 27 { 28 if(is[idx]) return 1ll << cnt; 29 return 1; 30 } 31 32 int normal() 33 { 34 int ret = 0; 35 memset(f, 0, sizeof(f)); 36 f[1][0][0][0][0][0] = 1; 37 //前i种牌,选了j组面子/杠子,k组雀头,其中第i-2~i种牌分别选了l,m,n张时,前i-3种牌可以获得的最大价值 38 for(int i = 1; i <= 34; ++i) 39 for(int j = 0; j <= 4; ++j) 40 for(int k = 0; k <= 1; ++k) 41 for(int l = 0; l <= 4; ++l) 42 for(int m = 0; m <= 4; ++m) 43 for(int n = 0; n <= 4; ++n) 44 { 45 int now = f[i][j][k][l][m][n]; 46 if(!now) continue; 47 if(i < 34) //选下一种牌 48 f[i + 1][j][k][m][n][0] = max(f[i + 1][j][k][m][n][0], now * (i > 2 ? c[a[i - 2]][l] : 1) * bao(i - 2, l)); 49 if(j < 4 && shunzi[i] && a[i] - n && a[i - 1] - m && a[i - 2] - l) //顺子 50 f[i][j + 1][k][l + 1][m + 1][n + 1] = max(f[i][j + 1][k][l + 1][m + 1][n + 1], now); 51 if(j < 4 && a[i] - n >= 3) //刻子 52 f[i][j + 1][k][l][m][n + 3] = max(f[i][j + 1][k][l][m][n + 3], now); 53 if(j < 4 && a[i] - n >= 4) //杠子 54 f[i][j + 1][k][l][m][n + 4] = max(f[i][j + 1][k][l][m][n + 4], now); 55 if(k < 1 && a[i] - n >= 2) //雀头 56 f[i][j][k + 1][l][m][n + 2] = max(f[i][j][k + 1][l][m][n + 2], now); 57 if(i == 34 && j == 4 && k == 1) 58 ret = max(ret, now * c[a[i - 2]][l] * bao(i - 2, l) * c[a[i - 1]][m] * bao(i - 1, m) * c[a[i]][n] * bao(i, n)); 59 } 60 return ret; 61 } 62 63 int seven_duizi() 64 { 65 memset(seven, 0, sizeof(seven)); 66 seven[0][0] = 1; 67 for(int i = 1; i <= 34; ++i) 68 for(int j = 0; j <= 7; ++j) 69 { 70 if(!seven[i - 1][j]) continue; 71 seven[i][j] = max(seven[i][j], seven[i - 1][j]); 72 if(j < 7 && a[i] >= 2) seven[i][j + 1] = max(seven[i][j + 1], seven[i - 1][j] * c[a[i]][2] * bao(i, 2)); 73 } 74 return seven[34][7] * 7; 75 } 76 77 int guoshi() 78 { 79 int ret = 0; 80 for(int i = 0; i < 13; ++i) 81 { 82 if(a[wushuang[i]] == 0) return 0; 83 if(a[wushuang[i]] == 1) continue; 84 int temp = c[a[wushuang[i]]][2] * bao(wushuang[i], 2); 85 for(int j = 0; j < 13; ++j) 86 { 87 if(i == j) continue; 88 temp = temp * c[a[wushuang[j]]][1] * bao(wushuang[j], 1); 89 } 90 ret = max(ret, temp * 13); 91 } 92 return ret; 93 } 94 95 signed main() 96 { 97 for(int i = 0; i <= 5; ++i) 98 { 99 c[i][0] = c[i][i] = 1; 100 for(int j = 1; j < i; ++j) c[i][j] = c[i - 1][j] + c[i - 1][j - 1]; 101 } 102 char str[5]; int T; 103 scanf("%lld", &T); 104 while(T--) 105 { 106 for(int i = 1; i <= 34; ++i) a[i] = 4; 107 memset(is, 0, sizeof(is)); 108 while(scanf("%s", str) != EOF && str[0] != '0') --a[getid(str)]; 109 while(scanf("%s", str) != EOF && str[0] != '0') is[getid(str)] = 1; 110 int ans = 0; 111 ans = max(ans, normal()); 112 ans = max(ans, seven_duizi()); 113 ans = max(ans, guoshi()); 114 printf("%lld\n", ans); 115 } 116 return 0; 117 }
T3:
这是啥?
计算几何?
玄学贪心?
反正不会。
Day2:
T1:
考场上以为只要确定了两块$1*1$砖的位置,就确定了一种方案。
但是两块$1*2$的砖块横着放和竖着放的方案竟然也是不同的。
于是这题就成功把我筛掉了。。。
设$a_i$表示$N = i$时的方案数。
先算一下最右侧不放方块时的方案。
当最右侧多出一列时,我们可以放一个竖着的方块,也可以横着放两个方块。
然后考虑在最右侧放一个方块时的方案数。
发现剩下的方块能放在$1~i-2$行,且每列恰好只有一个位置可行。
当这个方块放在第$j$行时,容易发现前面的方案数总共有$Fib_{j-1}$种,而两个$1*1$方块之间的方案数是唯一的。
所以有:$a_n = a_{n - 1} + a_{n - 2} + 2 * \sum_{i = 1}^{n - 3}Fib_i$(乘2是因为对称性,其中$Fib_0 = 1$)
又因为斐波那契数列满足性质:$\sum_{i=1}^{n}Fib_i = Fib_{i + 2} - 1$
所以有:$a_n = a_{n - 1} + a_{n - 2} + 2 * Fib_{n - 1} - 2$
为了方便我们令:$Fib_1 = 1$,即:
$$a_n = a_{n - 1} + a_{n - 2} + 2 * Fib_n - 2$$
矩阵快速幂即可。
$$
\begin{bmatrix}
a_{n} \\
a_{n - 1} \\
Fib_{n} \\
Fib_{n - 1} \\
2
\end{bmatrix}
=
\begin{bmatrix}
1 & 1 & 2 & 2 & -1 \\
1 & 0 & 0 & 0 & 0 \\
0 & 0 & 1 & 1 & 0 \\
0 & 0 & 1 & 0 & 0 \\
0 & 0 & 0 & 0 & 1
\end{bmatrix}
*
\begin{bmatrix}
a_{n - 1} \\
a_{n - 2} \\
Fib_{n - 1} \\
Fib_{n - 2} \\
2
\end{bmatrix}
$$
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 #define int long long 5 const int MOD = 1000000007; 6 7 struct Matrix 8 { 9 int a[6][6]; 10 }A, B; 11 12 Matrix mul(Matrix x, Matrix y) 13 { 14 Matrix z; memset(z.a, 0, sizeof(z.a)); 15 for(int i = 1; i <= 5; ++i) 16 for(int j = 1; j <= 5; ++j) 17 for(int k = 1; k <= 5; ++k) 18 z.a[i][j] = ((z.a[i][j] + x.a[i][k] * y.a[k][j] % MOD) % MOD + MOD) % MOD; 19 return z; 20 } 21 22 int T, n; 23 24 signed main() 25 { 26 scanf("%lld", &T); 27 while(T--) 28 { 29 scanf("%lld", &n); 30 if(n <= 2) {puts("0"); continue;} 31 n -= 2; 32 memset(A.a, 0, sizeof(A.a)); 33 memset(B.a, 0, sizeof(B.a)); 34 A.a[1][1] = A.a[1][2] = 1, A.a[1][3] = A.a[1][4] = 2, A.a[1][5] = -1; 35 A.a[2][1] = 1, A.a[3][3] = A.a[3][4] = 1, A.a[4][3] = A.a[5][5] = 1; 36 B.a[1][1] = B.a[2][2] = B.a[3][3] = B.a[4][4] = B.a[5][5] = 1; 37 for(; n; n >>= 1) 38 { 39 if(n & 1) B = mul(B, A); 40 A = mul(A, A); 41 } 42 printf("%lld\n", (B.a[1][3] + B.a[1][4] % MOD + B.a[1][5] * 2ll % MOD) % MOD); 43 } 44 return 0; 45 }
T2:
先来考虑这样一个问题:有两个集合$A,B$,我要从这两个集合里分别选一个点,求所有点对之间“两两最短路”的最小值。
我们建一个源点和汇点,然后把源点和$A$里的点都连上一条权值为0的有向边,把$B$里的点都和汇点连一条权值为0的有向边。
然后从以$s$为起点,$t$为终点跑一边最短路,再把之前连的边全部反向,再跑一遍最短路。两次的答案取个$min$即为最小值。
现在我们是要从一个集合里面选两个点,我们利用二进制的方式来把这个集合划分为两个集合,从所有划分的答案中取最小值即可。
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 #define int long long 5 const int inf = 0x3f3f3f3f3f3f3f3f; 6 const int MAXN = 100010; 7 const int MAXM = 600010; 8 9 struct Node 10 { 11 int u, dis; 12 bool operator < (const Node &x) const 13 { 14 return dis > x.dis; 15 } 16 }; 17 priority_queue<Node> q; 18 19 int T, n, m, k, s, t, ans, node[MAXN], dis[MAXN]; 20 int from[MAXM], to[MAXM], val[MAXM]; 21 int tot, Head[MAXN], ver[MAXM], Next[MAXM], Edge[MAXM]; 22 23 void add(int u, int v, int z) 24 { 25 ver[++tot] = v; 26 Edge[tot] = z; 27 Next[tot] = Head[u]; 28 Head[u] = tot; 29 } 30 31 int dijkstra() 32 { 33 for(int i = 1; i <= n + 2; ++i) dis[i] = inf; 34 while(!q.empty()) q.pop(); 35 q.push((Node){s, 0}); dis[s] = 0; 36 while(!q.empty()) 37 { 38 Node x = q.top(); q.pop(); 39 if(dis[x.u] != x.dis) continue; 40 for(int i = Head[x.u]; i; i = Next[i]) 41 { 42 int y = ver[i], z = Edge[i]; 43 if(dis[y] > dis[x.u] + z) 44 { 45 dis[y] = dis[x.u] + z; 46 q.push((Node){y, dis[y]}); 47 } 48 } 49 } 50 return dis[t]; 51 } 52 53 void build() 54 { 55 memset(Head, 0, sizeof(Head)); tot = 0; 56 for(int i = 1; i <= m; ++i) add(from[i], to[i], val[i]); 57 } 58 59 signed main() 60 { 61 scanf("%lld", &T); 62 while(T--) 63 { 64 scanf("%lld %lld %lld", &n, &m, &k); s = n + 1, t = s + 1; ans = inf; 65 for(int i = 1; i <= m; ++i) scanf("%lld %lld %lld", &from[i], &to[i], &val[i]); 66 for(int i = 1; i <= k; ++i) scanf("%lld", &node[i]); 67 for(int i = 0; (1 << i) <= k; ++i) 68 { 69 build(); 70 for(int j = 1; j <= k; ++j) 71 { 72 if((j >> i) & 1) add(s, node[j], 0); 73 else add(node[j], t, 0); 74 } 75 ans = min(ans, dijkstra()); 76 build(); 77 for(int j = 1; j <= k; ++j) 78 { 79 if((j >> i) & 1) add(node[j], t, 0); 80 else add(s, node[j], 0); 81 } 82 ans = min(ans, dijkstra()); 83 } 84 printf("%lld\n", ans); 85 } 86 return 0; 87 }
T3:
20分暴力走人。
出考场后听说写一波主席树可以拿链的部分分?
正解不会,溜了溜了。