2019牛客多校训练第二场题解
2019牛客多校训练第二场题解
A.Eddy Walker
打表即可发现答案为\(\frac{1}{n-1}\),注意特判。
证明的话\(1\)~\(n-1\)会均分概率,因为对于这些数来说走法都是一样的:假设当前为\(i\),而目前走到了\(i-1\)或者\(i+1\),之后就相当于一条链上的随机游走了。
Code
```cpp #includeB.Eddy Walker2
对于\(n>=0\)的时候直接上BM就行了。
对于\(n=-1\)的情况,走一步的期望长度为\(\frac{k+1}{2}\),也就是说.....我也不知道了,打表吧。。不过听有大佬分析说对于无穷远的点所走的步数可以看作相同的,概率均等,所以就是\(\frac{k+1}{2}\)(雾)。
Code
```cpp #includetypedef vector
typedef long long ll;
ll powMOD(ll a, ll b) {
ll ans = 1;
for (; b; b >>= 1, a = a * a%MOD)if (b & 1)ans = ans * a%MOD;
return ans;
}
ll n;
namespace linear_seq {
const int N = 10010;
ll res[N], base[N], _c[N], _md[N];
vector<int> Md;
void mul(ll *a, ll *b, int k) {
rep(i, 0, k + k) _c[i] = 0;
rep(i, 0, k) if (a[i]) rep(j, 0, k) _c[i + j] = (_c[i + j] + a[i] * b[j]) % MOD;
for (int i = k + k - 1; i >= k; i--) if (_c[i])
rep(j, 0, SZ(Md)) _c[i - k + Md[j]] = (_c[i - k + Md[j]] - _c[i] * _md[Md[j]]) % MOD;
rep(i, 0, k) a[i] = _c[i];
}
int solve(ll n, VI a, VI b) { // a 系数 b 初值 b[n+1]=a[0]*b[n]+...
ll ans = 0, pnt = 0;
int k = SZ(a);
assert(SZ(a) == SZ(b));
rep(i, 0, k) _md[k - 1 - i] = -a[i]; _md[k] = 1;
Md.clear();
rep(i, 0, k) if (_md[i] != 0) Md.push_back(i);
rep(i, 0, k) res[i] = base[i] = 0;
res[0] = 1;
while ((1ll << pnt) <= n) pnt++;
for (int p = pnt; p >= 0; p--) {
mul(res, res, k);
if ((n >> p) & 1) {
for (int i = k - 1; i >= 0; i--) res[i + 1] = res[i]; res[0] = 0;
rep(j, 0, SZ(Md)) res[Md[j]] = (res[Md[j]] - res[k] * _md[Md[j]]) % MOD;
}
}
rep(i, 0, k) ans = (ans + res[i] * b[i]) % MOD;
if (ans < 0) ans += MOD;
return ans;
}
VI BM(VI s) {
VI C(1, 1), B(1, 1);
int L = 0, m = 1, b = 1;
rep(n, 0, SZ(s)) {
ll d = 0;
rep(i, 0, L + 1) d = (d + (ll)C[i] * s[n - i]) % MOD;
if (d == 0) ++m;
else if (2 * L <= n) {
VI T = C;
ll c = MOD - d * powMOD(b, MOD - 2) % MOD;
while (SZ(C) < SZ(B) + m) C.pb(0);
rep(i, 0, SZ(B)) C[i + m] = (C[i + m] + c * B[i]) % MOD;
L = n + 1 - L; B = T; b = d; m = 1;
}
else {
ll c = MOD - d * powMOD(b, MOD - 2) % MOD;
while (SZ(C) < SZ(B) + m) C.pb(0);
rep(i, 0, SZ(B)) C[i + m] = (C[i + m] + c * B[i]) % MOD;
++m;
}
}
return C;
}
int gao(VI a, ll n) {
VI c = BM(a);
c.erase(c.begin());
rep(i, 0, SZ(c)) c[i] = (MOD - c[i]) % MOD;
return solve(n, c, VI(a.begin(), a.begin() + SZ(c)));
}
};
inline void add(int &x, int y) {
x += y;
if (x >= MOD)x -= MOD;
}
int t, k, dp[MAXN];
vector
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> t;
while (t--) {
v.clear();
cin >> k >> n;
if (n == -1) {
cout << 2 * powMOD(k + 1, MOD - 2) % MOD << '\n';
continue;
}
ll inv = powMOD(k, MOD - 2);
dp[1] = 1; v.push_back(1);
for (int i = 2; i <= 2 * k; i++) {
dp[i] = 0;
for (int j = max(1, i - k); j < i; j++) {
add(dp[i], dp[j]);
}
dp[i] = dp[i] * inv%MOD;
v.push_back(dp[i]);
}
cout << linear_seq::gao(v, n) << '\n';
}
return 0;
}
</details>
### <div class = "important_p">D.Kth Minimum Clique</div>
看了题解之后感觉做法挺自然的,但考场上还是没做出来= =
做法就是直接贪心去找团就行了,类似于dijkstra,队列附加一个bitset用来维护有哪些团就ok了。
<details>
<summary>Code</summary>
```cpp
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = a; i < b; i++)
using namespace std;
typedef long long ll;
const int N = 105;
int n, k;
int w[N];
char s[N];
bitset <N> f[N];
struct node{
ll ans;
bitset <N> S;
bool operator < (const node &A)const {
return ans > A.ans;
}
};
priority_queue <node> q;
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> k;
rep(i, 0, n) cin >> w[i];
rep(i, 0, n) { cin >> s;
rep(j, 0, n) if(s[j] == '1') f[i][j] = 1;
}
bitset <N> t; t.reset();
q.push(node{0, t}) ;
while(!q.empty()) {
node now = q.top(); q.pop();
if(--k == 0) {
cout << now.ans;
return 0;
}
bitset <N> tmp = now.S;
int p = 0;
rep(i, 0, n) if(tmp[i] == 1) p = i + 1;
rep(i, p, n) {
if((tmp & f[i]) == tmp) {
tmp[i] = 1;
q.push(node{now.ans + w[i], tmp});
tmp[i] = 0;
}
}
}
cout << -1;
return 0;
}
E.MAZE
第一次遇见这种题,原来线段树还可以这样用= =
首先考虑dp,有式子:\(dp[i][j]=\sum dp[i-1][k1]+\sum dp[i-1][k2]\)对于\(1\leq k1\leq j\leq k2\)且\(maze[i][k1]=maze[i][k1+1]=\cdots=maze[i][j]=0,maze[i][k2]=\cdots=maze[i][j]=0\),分别对应两个求和式子。含义可以自己脑补。
现在就相当于一层一层进行转移,并且转移时与当前行有关。
那么我们对每一行构造一个表示“列可达”的矩阵,之后所有的乘起来就是方案数了(矩阵乘法常用在计数方面)
因为题目涉及到修改,但每次也只针对一行,然后我们每次询问都是将所有的乘起来,所以直接把矩阵挂树上就行了。
修改时间复杂度\(O(logn*m^3)\),询问可做到\(O(1)\),建树时复杂度为\(O(n*m^3)\)。
代码如下:
Code
```cpp #includeF.Partition problem
直接爆搜就行了,然后在中间统计答案可以优化一层\(n\),做到\(C(2*N,N)*N\)的复杂度。
可以这样想,搜索树是一颗满二叉树,最后一层为叶子,我们将所有的\(n\)分散到前面的层数上去,因为前面的结点个数加起来还比最后一层结点个数少一个,所以复杂度就降下来了比起直接在最后进行统计。
Code
```cpp #includeH.Second Large Rectangle
做法比较多,悬线法、并查集、单调栈这些都可以搞,注意一点就是题目要求次大,所以我们统计答案的时候直接用set来存要被卡,只需要保留最大的两个就行了。
见代码吧:
Code
```cpp #includeJ.Subarray
因为给出的区间长度最多为\(1e7\),所以我们可以把有用的位置给拿出来,很容易知道最多\(3e7\)个位置有用。
之后将多个可以连在一起的段合并,然后对于每一段,求出前缀和,对于每一个位置,之前比它小的数都会产生贡献。因为前缀和中任意相邻两项相差为1,所以可以开个桶来搞,边维护边统计答案。
思路差不多就是这样,代码还有一些细节。求连续段的话可以类似于dp的方式来搞,通过记录向右、向左最大延伸。
细节见代码:
Code
```cpp #include</details>
重要的是自信,一旦有了自信,人就会赢得一切。