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分暴力走人。

出考场后听说写一波主席树可以拿链的部分分?

正解不会,溜了溜了。


 

posted @ 2019-04-16 19:14  Aegir  阅读(373)  评论(0编辑  收藏  举报