AtCoder Beginner Contest 291 解题记录

A - camel Case

题意:给定一个字符串,找到唯一存在的大写字母的位置

S是一个长度在2和100之间的字符串,由大写和小写英文字母组成。
S正好有一个大写字母。

#include
#include
#include
#include
#include
#include
#include
#include 
#include
#include
#include
#include
#define fi first
#define se second
using namespace std;
using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
const int dir[4][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };
const int N = 2e4 + 5;
int main() {

    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    string s;
    cin >> s;
    int cnt = 1;
    for (char c : s) {
        if (isupper(c)) {
            cout << cnt;
            break;
        }
        ++cnt;
    }
    return 0;
}

B - Trimmed Mean

题意:给你一个长度为 5 N 5N 5N的数组,剔除 N N N个最小值, N N N个最大值,求剩下的数的平均值

  • 1 ≤ N ≤ 100 1≤N≤100 1N100
  • 0 ≤ X i ≤ 100 0\leq X_i≤100 0Xi100
#include
#include
#include
#include
#include
#include
#include
#include 
#include
#include
#include
#include
#define fi first
#define se second
using namespace std;
using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
const int dir[4][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };
const int N = 2e4 + 5;
int main() {

    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    int cnt = n;
    n *= 5;
    vector<int> a(n);
    for (int i = 0; i < n; i++) cin >> a[i];
    sort(a.begin(), a.end());
    double ans = 0;
    for (int i = cnt; i < n - cnt; i++) {
        ans += a[i];
    }
    printf("%.8f", ans / (n - 2 * cnt));
    return 0;
}

C - LRUD Instructions 2

题意:你在二维平面的起点 ( 0 , 0 ) (0,0) (0,0)上,每次可以上下左右移动,给你一个字符串 S S S表示移动序列

  • ( x + 1 , y ) (x+1,y) (x+1,y) if the i-th character of S is R;
  • ( x − 1 , y ) (x-1,y) (x1,y) if the i-th character of S is L;
  • ( x , y + 1 ) (x,y+1) (x,y+1) if the i-th character of S is U;
  • ( x , y − 1 ) (x,y-1) (x,y1) if the i-th character of S is D,

你需要判断该移动序列有没有经过重复点的情况

1 ≤ N ≤ 2 × 1 0 5 1≤N≤2×10^5 1N2×105

思路: 考虑数据范围小,用set模拟即可

#include
#include
#include
#include
#include
#include
#include
#include 
#include
#include
#include
#include
#define fi first
#define se second
using namespace std;
using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
const int dir[4][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };
const int N = 2e4 + 5;
int main() {

    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    set<pii> s;
    int n;
    cin >> n;
    pii st = { 0, 0 };
    s.insert(st);
    bool flag = 0;
    for (int i = 0; i < n; i++) {
        char c;
        cin >> c;
        if (c == 'R') st = { st.first + 1, st.second };
        if (c == 'L') st = { st.first - 1, st.second };
        if (c == 'U') st = { st.first, st.second + 1};
        if (c == 'D') st = { st.first, st.second - 1};
        if (s.count(st)) flag = 1;
        s.insert(st);
    }
    if (flag) cout << "Yes";
    else cout << "No";
    return 0;
}

D - Flip Cards

题意:给你 n n n 张牌,每张牌的正面为 A [ i ] A[i] A[i] ,反面为 B [ i ] B[i] B[i] ,初始时所有牌都在正面,你可以翻转0或任意多张牌,使得对于每一对相邻的牌,写在其正面的整数是不同的,统计该方案数并mod 998244353

  • 1 ≤ N ≤ 2 × 1 0 5 1\le N\le 2\times10^5 1N2×105
  • 1 ≤ A [ i ] , B [ i ] ≤ 1 0 9 1\le A[i],B[i]\le10^9 1A[i],B[i]109

思路: 简单的递推dp,令 d p [ i ] [ 0 / 1 ] dp[i][0/1] dp[i][0/1]表示前 i i i 张牌中相邻的牌均不相同的方案数,且第 i i i 张牌为 A [ i ] / B [ i ] A[i]/B[i] A[i]/B[i],每次与前面一张不相同的牌转移即可

#include
#include
#include
#include
#include
#include
#include
#include 
#include
#include
#include
#include
#define fi first
#define se second
using namespace std;
using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
const int dir[4][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };
const int N = 2e5 + 5;
ll a[N][2], dp[N][2];
int mod = 998244353;
int main() {

    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    dp[0][0] = 1;
    for (int i = 1; i <= n; i++) cin >> a[i][0] >> a[i][1];
    for (int i = 1; i <= n; i++) {
        if (a[i][0] != a[i - 1][0])
            dp[i][0] = (dp[i][0] + dp[i - 1][0]) % mod;
        if (a[i][0] != a[i - 1][1])
            dp[i][0] = (dp[i][0] + dp[i - 1][1]) % mod;
        if (a[i][1] != a[i - 1][0])
            dp[i][1] = (dp[i][1] + dp[i - 1][0]) % mod;
        if (a[i][1] != a[i - 1][1])
            dp[i][1] = (dp[i][1] + dp[i - 1][1]) % mod;
    }
    cout << (dp[n][0] + dp[n][1]) % mod;
    return 0;
}

E - Find Permutation

题意:给定一个长度为 n n n 的排列 A A A,给你 m m m 对关系 ( X i , Y i ) (X_i,Y_i) (Xi,Yi) 表示 A X i < A Y i A_{X_i}AXi<AYi,你需要判断根据这些关系能否唯一确定该排列的顺序

  • 2 ≤ N ≤ 2 × 1 0 5 2\le N\le 2\times 10^5 2N2×105
  • 1 ≤ M ≤ 2 × 1 0 5 1\le M\le 2\times 10^5 1M2×105
  • 1 ≤ X i , Y i ≤ N 1\le X_i,Y_i\le N 1Xi,YiN

Sample Input 1

3 2
3 1
2 3

Sample Output 1

Yes
3 1 2

We can uniquely determine that A=(3,1,2).

Sample Input 2

3 2
3 1
3 2

Sample Output 2

No

Two sequences (2,3,1) and (3,2,1) can be A.

Sample Input 3

4 6
1 2
1 2
2 3
2 3
3 4
3 4

Sample Output 3

Yes
1 2 3 4

思路: 拓扑排序即可,如果存在多个入度为0的点或者拓扑排序发现环,则无法确定该排列

#include
#include
#include
#include
#include
#include
#include
#include 
#include
#include
#include
#include
#include
#define fi first
#define se second
using namespace std;
using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
const int dir[4][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };
const int maxn = 2e5 + 5;
int n, m;
vector<int> e[maxn];
int degree[maxn];
int a[maxn];
stack<int> s;

bool topo() {
	int sz = 0;
	bool finished = true;
	for (int i = 1; i <= n; i++) {
		if (!degree[i]) s.push(i);
	}
	while (!s.empty()) {
		if (s.size() > 1) finished = false;
		int k = s.top();
		a[sz++] = k;
		s.pop();
		for (int i = 0; i < e[k].size(); i++)
			if (--degree[e[k][i]] == 0)
				s.push(e[k][i]);
	}
	if (sz < n || !finished) return false;
	return true;
}
int main() {

	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin >> n >> m;
	while (m--) {
		int u, v;
		cin >> u >> v;
		e[u].push_back(v);
		degree[v]++;
	}
	if (!topo()) {
		cout << "No" << "\n";
	}
	else {
		cout << "Yes" << "\n";
		vector<int> ans(n + 1);
		int cnt = 1;
		for (int i = 0; i < n; i++) {
			ans[a[i]] = cnt++;
		}
		for (int i = 1; i <= n; i++) cout << ans[i] << " ";
	}
	return 0;
}

F - Teleporter and Closed off

题意:有 N N N 个城市,给定一个 N × M N\times M N×M的矩阵 S S S,若 S [ i ] [ j ] = 1 S[i][j]=1 S[i][j]=1则表示城市 i i i i + j i+j i+j 之间有一条有向边,边权均为 1,对于 k ∈ [ 2 , N ] k\in[2,N] k[2,N],你需要回答从城市1到城市 N N N且不经过城市 k k k的最短路,若不能到达则输出 − 1 -1 1

  • 3 ≤ N ≤ 1 0 5 3\le N\le 10^5 3N105
  • 1 ≤ M ≤ 10 1\le M\le 10 1M10
  • M < N MM<N
  • 如果 i + j > N i+j>N i+j>N S [ i ] [ j ] = 0 S[i][j]=0 S[i][j]=0
  • S [ i ] [ j ] ∈ [ 0 , 1 ] S[i][j]\in[0,1] S[i][j][0,1]

Sample Input 1

5 2
11
01
11
10
00

Sample Output 1

2 3 2

Sample Input 2

6 3
101
001
101
000
100
000

Sample Output 2

-1 3 3 -1

思路: 本题有两个特殊之处:路径的各结点编号是递增的, M M M范围比较小。我们先正反向建图,跑两遍 dijkstra \text{dijkstra} dijkstra预处理出从起点出发到各结点的最短路 d 1 [ i ] d1[i] d1[i],以及从终点出发到各结点的最短路 d 2 [ i ] d2[i] d2[i]。对于每次需要去除的顶点 k k k,我们需要枚举每一条边 ( i , j ) (i,j) (i,j)满足 i ∈ [ 1 , k − 1 ] , j ∈ [ k + 1 , N ] i\in[1,k-1],j\in[k+1,N] i[1,k1],j[k+1,N],则不经过顶点 k k k 的最短路即为
a n s = m i n i ∈ [ 1 , k − 1 ] j ∈ [ k + 1 , N ] S [ i ] [ j ] = 1 { d 1 [ i ] + d 2 [ j ] + 1 } ans=\mathop{min}\limits_{\substack{i\in[1,k-1]\\j\in[k+1,N]\\S[i][j]=1}}\{d1[i]+d2[j]+1\} ans=i[1,k1]j[k+1,N]S[i][j]=1min{d1[i]+d2[j]+1}
由于 j − i ≤ M , 1 ≤ M ≤ 10 j-i\le M,1\le M\le 10 jiM,1M10的特性,我们暴力枚举矩阵的 [ k − m + 1 , k − 1 ] [k-m+1, k-1] [km+1,k1]行即可

时间复杂度为: O ( N M 2 + E log ⁡ N ) O(NM^2+E\log N) O(NM2+ElogN) E E E为图的边数

#include
#include
#include
#include
#include
#include
#include
#include 
#include
#include
#include
#include
#include
#define fi first
#define se second
using namespace std;
using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
const int dir[4][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };
const int maxn = 1e6 + 5;
ll inf = (1ll << 50);
int n, m, s, t;
vector<vector<pii>> e1, e2;
vector<ll> d1, d2;
void dijkstra(int s, vector<vector<pii>>& e, vector<ll>& d) {
    vector<bool> vis(n + 1);
    priority_queue<pll> q;
    q.push({ d[s] = 0, s });
    while (!q.empty()) {
        pll now = q.top(); q.pop();
        int u = now.second;
        if (vis[u]) continue;
        vis[u] = 1;
        for (auto[to, w] : e[u]) {
            if (d[to] > d[u] + w) {
                d[to] = d[u] + w;
                q.push({ -d[to], to });
            }
        }
    }
}
int main() {

    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    s = 1, t = n;
    e1.resize(n + 1), e2.resize(n + 1), d1.resize(n + 1, inf), d2.resize(n + 1, inf);
    vector<string> g(n + 1);
    for (int i = 1; i <= n; i++) {
        string s;
        cin >> s;
        s = " " + s;
        g[i] = s;
        for (int j = 1; j < s.size(); j++) {
            if (s[j] == '1') {
                e1[i].push_back({ i + j, 1 });
                e2[i + j].push_back({ i, 1 });
            }
        }
    }
    dijkstra(s, e1, d1);
    dijkstra(t, e2, d2);
    for (int i = 2; i <= n - 1; i++) {
        ll ans = inf;
        for (int j = i - 1; j >= 1 && j > i - m; j--) {
            for (int k = 1; k <= m; k++) {
                if (j + k > i && g[j][k] == '1')
                    ans = min(ans, d1[j] + d2[j + k] + 1);
            }
        }
        if (ans == inf) cout << -1 << " ";
        else cout << ans << " ";
    }
    return 0;
}

F题有一个加强版

https://ac.nowcoder.com/acm/problem/14293?&headNav=acm

Delete

给定一张n个点,m条边的带权有向无环图,同时给定起点S和终点T,一共有q个询问,每次询问删掉某个点和所有与它相连的边之后S到T的最短路,询问之间互相独立(即删除操作在询问结束之后会立即撤销),如果删了那个点后不存在S到T的最短路,则输出-1。

输入描述:

第一行四个正整数表示n,m,S,T,意义如题所述;
接下来m行每行三个正整数x[i],y[i],z[i],表示有一条x[i]到y[i]的有向边,权值为z[i];
第m+1行一个正整数q表示询问次数;
接下来q行每行一个正整数a[i]表示这次询问要删除点a[i]。
n , q < = 1 0 5 n,q <= 10^5 n,q<=105
m < = 2 ∗ 1 0 5 m <= 2*10^5 m<=2105
z [ i ] < = 1 0 9 z[i] <= 10^9 z[i]<=109

输出描述:

q行每行一个数输出答案,如果删了这个点后不存在S到T的最短路,输出-1

输入
6 7 1 5
1 2 2
2 3 4
3 4 3
4 5 5
3 5 9
1 6 10
6 5 13
4
3
4
2
6
输出
23
15
23
14
思路

本题相较于上题,每一条边 ( i , j ) (i,j) (i,j)没有了 j − i ≤ M j-i\le M jiM的限制,同时路径的结点编号也不一定递增,且每条边权重不一定为1,但大体思路相同,先正反向建图,跑两遍 dijkstra \text{dijkstra} dijkstra预处理出从起点出发到各结点的最短路 d 1 [ i ] d1[i] d1[i],以及从终点出发到各结点的最短路 d 2 [ i ] d2[i] d2[i]。考虑拓扑序,对于删除的顶点 k k k ,一定有一条边 ( i , j ) (i,j) (i,j) k k k 的左边连接到 k k k 的右边, 即满足 t o p [ i ] < t o p [ k ] , t o p [ j ] > t o p [ k ] top[i]top[k] top[i]<top[k],top[j]>top[k]。我们可以枚举所有的边 ( i , j ) (i,j) (i,j),用 d 1 ( s , i ) + w ( i , j ) + d i s t ( j , t ) d1(s, i) + w(i, j) + dist(j, t) d1(s,i)+w(i,j)+dist(j,t),用线段树更新拓扑序中 [ t o p [ i ] + 1 , t o p [ j ] − 1 ] [top[i]+1, top[j]-1] [top[i]+1,top[j]1]的区间的最小值,查询时单点查询即可。

查询时有个地方需要注意,对于 d 1 [ i ] = i n f d1[i]=inf d1[i]=inf或者 d 2 [ i ] = i n f d2[i]=inf d2[i]=inf的点,删除后并不会对 s → t s\rightarrow t st的最短路产生影响,结果仍为 d 1 [ t ] d1[t] d1[t],由于它们并不在 s → t s\rightarrow t st的路径上,所以使用拓扑序并不一定能更新到这些点

AtCoder Beginner Contest 291 解题记录_第1张图片

他们的拓扑序为:

i 1 2 3 4 5 6 7 8
id[i] 1 4 2 5 6 7 3 8

如起点 s = 1 s=1 s=1,终点 t = 5 t=5 t=5,6和8的拓扑序比5的拓扑序更大,枚举边时不会被更新到,但删除他们的结果均为 1 → 2 → 5 = 6 1\rightarrow2\rightarrow5=6 125=6

#include
#include
#include
#include
#include
#include
#include
#include 
#include
#include
#include
#include
#include
#define fi first
#define se second
using namespace std;
using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
const int dir[4][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };
const int maxn = 1e6 + 5;
ll inf = (1ll << 50);
int n, m, s, t;
vector<vector<pii>> e1, e2;
vector<ll> d1, d2;
int id[maxn], fid[maxn], degree[maxn], pos;
struct tag {
    ll add;
    tag operator + (const tag& t) const {
        return { min(add, t.add) };
    }
    void clear() {
        add = inf;
    }
};
struct Node {
    int l, r;
    ll val;
    tag t;
}tr[4 * maxn];
void pushup(int u) {
    tr[u].val = min(tr[u << 1].val, tr[u << 1 | 1].val);
}
void settag(int u, tag& t) {
    tr[u].t = tr[u].t + t;
    tr[u].val = min(tr[u].val, tr[u].t.add);
}
void pushdown(int u) {
    settag(u << 1, tr[u].t);
    settag(u << 1 | 1, tr[u].t);
    tr[u].t.clear();
}
void build(int u, int l, int r) {
    tr[u] = { l, r, inf, {inf} };
    if (l == r) return;
    int mid = (l + r) >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
    pushup(u);
}
void update(int u, int l, int r, tag t) {
    if (tr[u].l >= l && tr[u].r <= r) {
        settag(u, t);
        return;
    }
    pushdown(u);
    int mid = (tr[u].l + tr[u].r) >> 1;
    if (l <= mid) update(u << 1, l, r, t);
    if (r > mid) update(u << 1 | 1, l, r, t);
    pushup(u);
}
ll query(int u, int l, int r) {
    if (tr[u].l >= l && tr[u].r <= r) {
        // 特判不在s->t路径上的点
        if (d1[fid[l]] == inf || d2[fid[l]] == inf) return d1[t];
        else return tr[u].val;
    }
    pushdown(u);
    int mid = (tr[u].l + tr[u].r) >> 1;
    if (l > mid) return query(u << 1 | 1, l, r);
    else if (r <= mid) return query(u << 1, l, r);
    else return query(u << 1, l, r) + query(u << 1 | 1, l, r);
}
void dijkstra(int s, vector<vector<pii>>& e, vector<ll>& d) {
    vector<bool> vis(n + 1);
    priority_queue<pll> q;
    q.push({ d[s] = 0, s });
    while (!q.empty()) {
        pll now = q.top(); q.pop();
        int u = now.second;
        if (vis[u]) continue;
        vis[u] = 1;
        for (auto [to, w] : e[u]) {
            if (d[to] > d[u] + w) {
                d[to] = d[u] + w;
                q.push({ -d[to], to });
            }
        }
    }
}
void topo() {
    queue<int> q;
    for (int i = 1; i <= n; i++) {
        if (degree[i] == 0) {
            q.push(i);
        }
    }
    while (!q.empty()) {
        int now = q.front(); q.pop();
        id[now] = ++pos;
        fid[pos] = now;
        for (auto [to, w] : e1[now]) {
            degree[to]--;
            if (!degree[to]) {
                q.push(to);
            }
        }
    }
}
int main() {

    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m >> s >> t;
    e1.resize(n + 1), e2.resize(n + 1), d1.resize(n + 1, inf), d2.resize(n + 1, inf);
    while (m--) {
        int u, v, w;
        cin >> u >> v >> w;
        degree[v]++;
        e1[u].push_back({ v, w });
        e2[v].push_back({ u, w });
    }
    dijkstra(s, e1, d1);
    dijkstra(t, e2, d2);
    topo();
    build(1, 1, n);
    for (int i = 1; i <= n; i++) {
        for (auto [j, w] : e1[i]) {
            if (id[i] != id[j] - 1 && d1[i] != inf && d2[j] != inf) {
                update(1, id[i] + 1, id[j] - 1, { d1[i] + d2[j] + w });
            }
        }
    }
    int q;
    cin >> q;
    while (q--) {
        int pos;
        cin >> pos;
        ll res = query(1, id[pos], id[pos]);
        cout << (res == inf ? -1 : res) << "\n";
    }
    return 0;
}
/*
8 7 1 5
1 2 2
2 5 4
3 4 5
4 6 6
3 8 7
7 8 8
4 8 5
*/

你可能感兴趣的:(算法笔记,c++,算法,图论,数据结构,动态规划)