A - 3,2,1,GO
签到
void zyb() {
int n;
cin >> n;
for(int i = n; i >= 1; i --) {
if(i == n) cout << i;
else cout << "," << i;
}
}
B - Split Ticketing
数据量够三层 for 循环
void zyb() {
int n;
cin >> n;
vector<vector<int>> C(n+1, vector<int>(n+1));
for(int i = 1; i <= n; i ++) {
for(int j = i+1; j <= n; j ++) {
cin >> C[i][j];
}
}
for(int i = 1; i <= n; i ++) {
for(int j = i+1; j <= n; j ++) {
for(int k = j+1; k <= n; k ++) {
if(C[i][k] > C[i][j]+C[j][k]) {
cout << "Yes\n";
return;
}
}
}
}
cout << "No\n";
}
C - Puddles
只需要先对所有边界位置的空地进行一次 bfs,将连通块变为 #,那么剩余的 . 联通块个数,就全部是不含边界位置的。
void zyb() {
int h, w;
cin >> h >> w;
vector<vector<char>> S(h+1, vector<char>(w+1));
vector<vector<int>> vis(h+1, vector<int>(w+1));
for(int i = 1; i <= h; i++) {
string s; cin >> s;
for(int j = 1; j <= w; j ++) {
S[i][j] = s[j-1];
}
}
auto bfs = [&](int i, int j)->void {
queue<pii> q;
q.push({i, j});
while(q.size() > 0) {
auto u = q.front();
q.pop();
int x = u.first, y = u.second;
if(vis[x][y]) continue;
S[x][y] = '#';
vis[x][y] = true;
for(int k = 0; k < 4; k ++) {
int xx = dx[k]+x, yy = dy[k]+y;
if(xx < 1 || xx > h || yy < 1 || yy > w) continue;
if(vis[xx][yy] || S[xx][yy] == '#') continue;
q.push({xx, yy});
}
}
};
for(int i = 1; i <= h; i ++) {
for(int j = 1; j <= w; j ++) {
if((i == 1 || i == h || j == 1 || j == w) && S[i][j] == '.') {
bfs(i, j);
}
}
}
int cnt = 0;
for(int i = 1; i <= h; i ++) {
for(int j = 1; j <= w; j ++) {
if(S[i][j] == '.') {
bfs(i, j);
cnt ++;
}
}
}
cout << cnt << "\n";
}
D - Minimize Range
要对所有数字加不定量个 k,由于:(a[i]+m*k)%k = a[i]%k,考虑对所有数取模。将 max-min 转化为:将这些数字映射到长度不超过 k 的环形区间上,求能覆盖所有数字的最小长度。
想象一个圆,所有数按大小顺时针在圆上。覆盖所有数,有两种方法:
- 从最小数顺时针一直覆盖到最大数
- 从任意某一个数开始,一直顺时针,穿过起点,一直覆盖到比它的前一个数为止
两种方法取最小值即可。
void zyb() {
/*
由给每个数反复多次 +k 想到 (a[i]+m*k)%k = a[i]%k
然后想到 a[i]%k 会永远不变,并且是小于 k 的
现在将最小化 max(a)-min(a) 这个问题
转化为让所有数落在一个尽可能短的"环形"区间内
这个最短的长度可能是 a[n]-a[1](可以包含全部数)
也可能是任意一个 ai 在环形圆里转个圈,超过一圈后返回 a(i-1)
这样也能覆盖所有的数,长度为 a[i]+k-a[i+1]
*/
int n, k;
cin >> n >> k;
vector<int> a(n + 1);
for(int i = 1; i <= n; i ++) {
cin >> a[i];
a[i] %= k;
}
sort(a.begin()+1, a.end());
int mi = a[n]-a[1];
for(int i = 1; i < n; i ++) {
mi = min(mi, a[i]+k-a[i+1]);
}
cout << mi << "\n";
}
E - Fibonacci String
根据这个类斐波那契数列的规则,字符串的个数在有限范围内不会超过90,因此,可以先将前90个规模的字符串的信息给处理出来。
问题是要求 [l,r] 里的 ch 字符数量,那么是可以转化为先求 [1,r] 里ch字符的,再求 [1,l-1]里的ch字符数量,然后相减。所以问题就转化为了在一个极长的目标字符串里,求 [1,k] 的某个字符的数量。
题目中的规律告诉我们,第i个字符串 Si,一定是第i+1个字符串 S(i+1)的前缀。
所以假设我们所要求的那个 k,长度上最大是小于第 x 个字符串的长度的话,那么它一定是大于第 x-1 个字符串的,并且因为第 x-1 个字符串是第 x 个字符串的前缀,所以第x-1个字符串里的东西,全部是我们所需要统计的,那么,我们可以先统计第 x-1 个,然后剩余的那些,肯定也是第 x-2 个字符串的前缀,同时,第 x-2 个字符串又由第 x-3 和第 x-4 个字符串组成…如何形成一个递归的思路
我们在使用 sz[i] 来记录第 i 个字符串的个数,每次当我们需要找当前需要的是第几个字符串的时候,只需要使用二分查找,找到大于当前长度的第一个字符串即可。下标减1后就可以得到我们可以完全统计的那个字符串。然后将 k 减去这个长度,继续循环找。
void zyb() {
string x, y;
cin >> x >> y;
// sz[i]:第i个中字符的个数
// numx[i]:第i个中包含字符串x的个数
// numy[i]:第i个中包含字符串y的个数
vector<i64> sz, numx, numy;
sz.push_back(x.size()), sz.push_back(y.size());
numx.push_back(1), numx.push_back(0);
numy.push_back(0), numy.push_back(1);
while(1) {
int len = sz.size(), lennx = numx.size(), lenny = numy.size();
sz.push_back(sz[len-1] + sz[len-2]);
numx.push_back(numx[lennx-1]+numx[lennx-2]);
numy.push_back(numy[lenny-1]+numy[lenny-2]);
if(sz.back() > lnf) break;
}
int n = x.size(), m = y.size();
// 对于单个字符串,每个字符出现的前缀和
vector<vector<int>> cntx(n+1, vector<int>(26, 0));
vector<vector<int>> cnty(m+1, vector<int>(26, 0));
for(int i = 1; i <= n; i ++) {
for(int j = 0; j < 26; j ++) {
cntx[i][j] = cntx[i-1][j];
if(x[i-1]-'a' == j) cntx[i][j] ++;
}
}
for(int i = 1; i <= m; i ++) {
for(int j = 0; j < 26; j ++) {
cnty[i][j] = cnty[i-1][j];
if(y[i-1]-'a' == j) cnty[i][j] ++;
}
}
// 假设 x = abc
// y = defgh
// 那么最终生成的字符串一定是 defghabc... y 字符串在前面
/* 由于 s[i] = s[i-1]+s[i-2]
那么从 s[i] 中减去一个前缀 s[i-k], 那么 s[i-k]一定是 s[i-k+1]的前缀
s[i-k+1] 一定是 s[i-k+2] 的前缀, 所以 s[i-k] 一定是 s[i] 的前缀
并且 s[i-k] 和 s[i-k-1] 也一定是连在一起的, s[i-k] 在前
由于找到的是最大的 idx, 所以接下来计算的长度, 一定是小于sz[i-k]的
那么剩余的没有计算的部分, 就一定是 s[i-k-1] 的前缀
*/
auto cal = [&](i64 c, char ch) -> i64 {
i64 sum = 0;
while(c > 0) {
if(c <= m) {
sum += cnty[c][ch-'a'];
break;
}
if(c < m+n) {
sum += cnty[m][ch-'a'] + cntx[c-m][ch-'a'];
break;
}
// c > n+m 说明一定是第三个字符串及其之后的字符串作为这部分的前缀
int idx = (upper_bound(sz.begin(), sz.end(), c)-sz.begin())-1;
sum += numx[idx]*cntx[n][ch-'a'] + numy[idx]*cnty[m][ch-'a'];
c -= sz[idx];
}
return sum;
};
int q;
cin >> q;
while(q --) {
i64 l, r;
char ch;
cin >> l >> r >> ch;
i64 ans = cal(r, ch) - cal(l-1, ch);
cout << ans << "\n";
}
}
F - Strongly Connected 2
图强连通(strongly connected)的条件:图中任意两个点u和v,都存在从u到v和从v到u的路径。
题目已经规定了,从所有的大于 1 的点 i 都一定有一条路可以到 i-1 点,那么我们只需要保证从 1 可以到 2,从 2 可以到 3,3 可以到 4,n-1 可以到 n 即可…
定义 dp[i] 为从 1 号点恰好能到 i 号点的方案数。
从小到大枚举每个点,假设 i 号点有一条通向 e 的路径,那么 dp[e] 应该加上 [i, e-1] 这个区间所有的贡献,因为 i+1 可以到 i,那么这些路径都可以先走到 i+k 这个点,再走到 i,然后走到 e,同时,因为这条边是由 i 走向 e 的,所以所有大于等于 e 的所有最远点都和这条边没有关系,但这条边有没有是两种状态,因此 dp[e], dp[e+1]......,dp[n] 都 × 2
同时细节是对于每个点的所有边,需要从大到小枚举可达点,这样,方案数乘 2 操作就不会影响到较小 e 的 sum(dp[i+1],dp[i+2],...dp[e-1]),更新就不会出错。
struct SegmentTree {
int n;
vector<i64> a;
vector<i64> sum, add, mul;
SegmentTree(vector<i64>& tmp) {
a = tmp;
n = tmp.size() - 1;
sum.resize(4 * n, 0), add.resize(4 * n, 0), mul.resize(4 * n, 1);
init(1, n, 1);
}
void up(int i) {
sum[i] = (sum[i * 2] + sum[i * 2 + 1]) % mod;
}
// (x + add) * mul x*mul + add*mul
void init(int l, int r, int i) {
if (l == r) {
sum[i] = a[l] % mod;
return;
}
int mid = (l + r) / 2;
init(l, mid, i * 2);
init(mid + 1, r, i * 2 + 1);
up(i);
}
void lazy_add(int i, i64 k, int l, int r) {
add[i] += k; add[i] %= mod;
sum[i] += (r - l + 1) * k; sum[i] %= mod;
}
void lazy_mul(int i, i64 k, int l, int r) {
mul[i] *= k; mul[i] %= mod;
sum[i] *= k; sum[i] %= mod;
add[i] *= k; add[i] %= mod;
}
void down(int i, int l, int r) {
int mid = (l + r) / 2;
if (mul[i] != 1) {
sum[i * 2] *= mul[i]; sum[i * 2] %= mod;
sum[i * 2 + 1] *= mul[i]; sum[i * 2 + 1] %= mod;
add[i * 2] *= mul[i]; add[i * 2] %= mod;
add[i * 2 + 1] *= mul[i]; add[i * 2 + 1] %= mod;
mul[i * 2] *= mul[i]; mul[i * 2] %= mod;
mul[i * 2 + 1] *= mul[i]; mul[i * 2 + 1] %= mod;
mul[i] = 1;
}
if (add[i]) {
lazy_add(i * 2, add[i], l, mid);
lazy_add(i * 2 + 1, add[i], mid + 1, r);
add[i] = 0;
}
}
void modify_mul(int l, int r, int i, int jl, int jr, i64 k) {
if (jl <= l && jr >= r) {
lazy_mul(i, k, l, r);
return;
}
down(i, l, r);
int mid = (l + r) / 2;
if (jl <= mid) modify_mul(l, mid, i * 2, jl, jr, k);
if (jr > mid) modify_mul(mid + 1, r, i * 2 + 1, jl, jr, k);
up(i);
}
void modify_add(int l, int r, int i, int jl, int jr, i64 k) {
if (jl <= l && jr >= r) {
lazy_add(i, k, l, r);
return;
}
down(i, l, r);
int mid = (l + r) / 2;
if (jl <= mid) modify_add(l, mid, i * 2, jl, jr, k);
if (jr > mid) modify_add(mid + 1, r, i * 2 + 1, jl, jr, k);
up(i);
}
i64 query(int l, int r, int i, int jl, int jr) {
if (jl <= l && jr >= r) {
return sum[i];
}
int mid = (l + r) / 2;
down(i, l, r);
i64 res = 0;
if (jl <= mid) res += query(l, mid, i * 2, jl, jr); res %= mod;
if (jr > mid) res += query(mid + 1, r, i * 2 + 1, jl, jr); res %= mod;
return res;
}
void modify_mul(int jobl, int jobr, i64 k) {
modify_mul(1, n, 1, jobl, jobr, k);
}
void modify_add(int jobl, int jobr, i64 k) {
modify_add(1, n, 1, jobl, jobr, k);
}
i64 query(int jobl, int jobr) {
return query(1, n, 1, jobl, jobr);
}
};
void zyb()
{
int n, m;
cin >> n >> m;
vector<vector<int>> edg(n + 1);
for(int i = 0; i < m; i ++) {
int u, v;
cin >> u >> v;
edg[u].push_back(v);
}
// dp[i]:1到达i,有多少种方法
vector<i64> dp(n + 1);
SegmentTree seg(dp);
seg.modify_add(1, 1, 1);
for(int i = 1; i <= n; i ++) {
sort(edg[i].begin(), edg[i].end(), greater<int>());
}
for(int i = 1; i <= n; i ++) {
for(int e : edg[i]) {
// dp[e] += (dp[1]...dp[e-1]);
i64 sum = seg.query(i, e-1);
seg.modify_mul(e, n, 2ll); // e->n 的全部乘 2
seg.modify_add(e, e, sum);
}
}
i64 ans = seg.query(n, n);
cout << ans << "\n";
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
// cin >> T;
while(T --) {
zyb();
}
return 0;
}
&spm=1001.2101.3001.5002&articleId=159394157&d=1&t=3&u=1fff049b9f29416fb6511aa43c1dba95)
469

被折叠的 条评论
为什么被折叠?



