2021 CSP-S2 题解(完整版)

[CSP-S 2021] 廊桥分配

一句话题意:

为国内航班和国际航分配廊桥的数量,使得最终停在廊桥的飞机总数最大。廊桥的使用原则是先到先得。

关键点

  1. 廊桥是先到先得,不是自由分配!
  2. 所有的时间点是不同的(这是树状数组优化的前提)
  3. 数据量 1 0 5 10^5 105, 复杂度确定为 n l g n nlgn nlgn级别,排序是必须的,则剩余的处理大致是一个O(n), 或加一个logn优化。

分析

  1. 由于先到先得,所以按照区间起点排序。
  2. 先不考虑廊桥的数量,单纯从为每个飞机分配廊桥的角度出发。通过随手画几个数据例子可以发现,当所有已经在使用的廊桥的结束时间都晚于当前飞机的到达时间时,必须为他单独分配一个新的廊桥。
  3. 如果已经在使用的廊桥中,存在结束时间早于当前飞机到达时间,则可以利用这个旧廊桥。
  4. 如果有多个旧廊桥可以利用,我们的选择是等价的。
  5. 由此我们希望能利用旧廊桥的飞机,尽量利用最早分配的廊桥。

通过上面的分析和结论,我们发现,用这样的过程来模拟可以得到第一个廊桥最多的服务次数,前两个廊桥最多的服务次数,依次类推。我们得到了不同数量的廊桥能服务的最大飞机数。

然后就暴力枚举分配廊桥数量,取最大就可以了。

错误思路

  1. 三分:两个增函数, x 1 + x 2 = n x_1+x_2=n x1+x2=n时,并不能唯一叠加出一个单峰函数,有可能是双峰函数,所以不行。

优化 - 树状数组(单点修改,前缀最值)

按照以上思路写代码,会出现一个不好解决的问题: 要在多个可用的旧廊桥中找编号最小的廊桥。使用暴力方法需要O(n) 的扫描,因此复杂度退化为 O ( n 2 ) O(n^2) O(n2)

这个操作很容易被抽象出来:在所有小于某个时间的编号中取最小值, 这明显是一个类似二维偏序的问题,所以使用树状数组来维护每个时间点对应的廊桥编号,动态取前缀最小值即可。

同是为了防止炸空间我还加了离散化。


#include 
using namespace std;

typedef long long ll;


template<class T> bool chmax(T &a, const T &b) { if (a<b) { a=b; return 1; } return 0; }
template<class T> bool chmin(T &a, const T &b) { if (a>b) { a=b; return 1; } return 0; }

const int MOD     = 1e9+7;
const int INF     = 0x3f3f3f3f;
const ll  LLINF   = 0x3f3f3f3f3f3f3f3f;
const int MAXN    = 500005;
const int MAXM    = 2000005;

ll vis[MAXN], tim[MAXN];
int n, m1, m2;

struct BIT {
    int C[MAXN];
    int N;

    inline int lowbit(int x) { return x & (-x); }

    void init(int _N) {
        N = _N;
        memset(C, 0x3f, sizeof(C));
    }

    int getMin(int x) {
        int ret = INF;
        while(x > 0) {
            ret = min(ret, C[x]);
            x -= lowbit(x);
        }
        return ret;
    }

    void updateMin(int x) {
        while(x<=N) {
            C[x] = tim[x];
            for(int i=1;i<lowbit(x); i<<=1) C[x] = min(C[x], C[x-i]);
            x += lowbit(x); 
        }
    }

    void DEBUG() {
        for(int i = 1; i <= N; i++) {
            cout << i << ": " << C[i] << endl;
        }
    }
}Bit;


void handle(vector<pair<int, int> > &A,  int F[]) {
    Bit.init(2*(m1+m2) + 2);
    sort(A.begin(), A.end());
    memset(tim, 0x3f, sizeof(tim));
    memset(vis, 0, sizeof(vis));
    int cnt = 0;
    for(int i = 0; i < A.size(); i++) {
        int id = Bit.getMin(A[i].first);
        // 没有可以停靠的廊桥
        if(id == INF) {
            cnt ++;
            F[cnt] = 1;
            tim[A[i].second] = cnt;
            vis[cnt] = A[i].second;
            Bit.updateMin(A[i].second);
        }
        // 有可以停靠的廊桥
        else {
            tim[vis[id]] = INF;
            Bit.updateMin(vis[id]);
            vis[id] = A[i].second;
            tim[A[i].second] = id;
            Bit.updateMin(A[i].second);
            F[id] ++;
        }
        // Bit.DEBUG();
    }
}


vector<pair<int, int> > A1, A2;
int F1[MAXN], F2[MAXN];
vector<int> pool;
int main(){

    cin >> n >> m1 >> m2;
    for(int i = 0; i < m1; i++) {
        int l, r; cin >> l >> r;
        A1.push_back(make_pair(l, r));
        pool.push_back(l);
        pool.push_back(r);
    }
    for(int i = 0; i < m2; i++) {
        int l, r; cin >> l >> r;
        A2.push_back(make_pair(l, r));
        pool.push_back(l);
        pool.push_back(r);
    }
    // 离散化
    sort(pool.begin(), pool.end());
    int cnt = unique(pool.begin(), pool.end()) - pool.begin();
    for(int i = 0; i < m1; i++) {
        A1[i].first = lower_bound(pool.begin(), pool.begin() + cnt, A1[i].first) - pool.begin() + 1;
        A1[i].second = lower_bound(pool.begin(), pool.begin() + cnt, A1[i].second) - pool.begin() + 1;
    }
    for(int i = 0; i < m2; i++) {
        A2[i].first = lower_bound(pool.begin(), pool.begin() + cnt, A2[i].first) - pool.begin() + 1;
        A2[i].second = lower_bound(pool.begin(), pool.begin() + cnt, A2[i].second) - pool.begin() + 1;
    }
    // 分别处理
    handle(A1, F1);
    handle(A2, F2);

    for(int i = 1; i <= n; i++) F1[i] += F1[i-1], F2[i] += F2[i-1];
    int ans = 0;
    for(int i = 0; i <= n; i++) {
        ans = max(ans, F1[i] + F2[n-i]);
    }
    cout << ans << endl;
    
    return 0;
}

  



[CSP-S 2021] 括号序列

一句话题意:

为?位置填充字符,求合法的序列数量。

关键点

  1. 数据范围是500, 妥妥 O ( n 3 ) O(n^3) O(n3), 可能略微卡常,写代码的时候注意一点就好了。
  2. 合法括号序列数量,结合复杂度,区间DP。

分析

本题的类型非常好确认,难点在于搞清楚合法的序列的定义,具体的包含了以下几种(两类, 1-3是包围类,4是并排类):

  1. ()
  2. (S)
  3. (A)、(AS)、(SA)
  4. ASA

搞清楚这几类之后,我们要的东西就呼之欲出了:

  1. dp[i][j][0]: [i, j] 区间,完全合法的括号方案数。
  2. dp[i][j][1]: [i, j] 区间,AS的括号方案数。
  3. dp[i][j][2]: [i, j] 区间,SA的括号方案数。
  4. dp[i][j][3]: [i, j] 区间,S的方案数。

易错点

有的同学考虑区间dp会导致重复的问题,因此我们将所有的合法括号分为两类,上面已经描述过。这两类之间是不重不漏的。其中第二类会出现重复计数的问题,因此我们枚举ASA中,第一个A的位置,并且要求这个A一定是一个包围类的A。则可以避免重复。


#include 
using namespace std;

typedef long long ll;
template<class T> bool chmax(T &a, const T &b) { if (a<b) { a=b; return 1; } return 0; }
template<class T> bool chmin(T &a, const T &b) { if (a>b) { a=b; return 1; } return 0; }

const int MOD     = 1e9+7;
const int INF     = 0x3f3f3f3f;
const ll  LLINF   = 0x3f3f3f3f3f3f3f3f;
const int MAXN    = 505;
const int MAXM    = 2000005;

ll dp[MAXN][MAXN][4], b[MAXN], vis[MAXN];
int n, m, k;
string s;

void DEBUG(int i, int j) {
    for(int p = i; p <= j; p++) printf("%c", s[p]);
    printf(" (%d, %d): %lld, %lld, %lld, %lld\n", i, j, dp[i][j][0], dp[i][j][1], dp[i][j][2], dp[i][j][3]);
}

int main(){
    cin >> n >> m;
    cin >> s;
    if(m >= 1) for(int i = 0; i < n; i++) {
        if(s[i] == '?' || s[i] == '*') dp[i][i][3] = 1;

        // DEBUG(i, i);
    }
    
    for(int L = 2; L <= n; L++) {
        for(int i = 0; i < n && i + L - 1 < n; i++) {
            int j = i + L - 1;
            // dp[i][j][0] 包围型(  )
            if(s[i] == '?' && s[j] == ')' || s[i] == '(' && s[j] == '?' || s[i] == '?' && s[j] == '?' || s[i] == '(' && s[j] == ')') {
                if(i+1 > j-1) dp[i][j][0] = 1;
                else dp[i][j][0] = ( dp[i][j][0] + dp[i+1][j-1][0] + dp[i+1][j-1][1] + dp[i+1][j-1][2] + dp[i+1][j-1][3] ) % MOD;
            }

            // dp[i][j][0] 并排型
            int f = 0;
            for(int k = i+1; k < j-1; k++) {
                // 枚举前一段的A
                if((s[i] == '(' || s[i] == '?') && (s[k] == ')' || s[k] == '?')) {
                    if(i+1 == k) {
                        // AB
                        dp[i][j][0] = ( dp[i][j][0] + dp[k+1][j][0] ) % MOD;
                        // A + SB
                        dp[i][j][0] = ( dp[i][j][0] + dp[k+1][j][2] ) % MOD;

                    }
                    else {
                        // AB
                        dp[i][j][0] = ( dp[i][j][0] + (dp[i+1][k-1][0] + dp[i+1][k-1][3] + dp[i+1][k-1][1] + dp[i+1][k-1][2]) * dp[k+1][j][0] ) % MOD;
                        // A + SB
                        dp[i][j][0] = ( dp[i][j][0] + (dp[i+1][k-1][0] + dp[i+1][k-1][3] + dp[i+1][k-1][1] + dp[i+1][k-1][2]) * dp[k+1][j][2] ) % MOD;
                    }

                }
                // AS + B 和 A + SB 重复
                // dp[i][j][0] = ( dp[i][j][0] + dp[i][k][1] * dp[k+1][j][0] ) % MOD;
            }
            // dp[i][j][1] AS型
            for(int k = j-1; k >= j - m && k > i; k--) {
                dp[i][j][1] = ( dp[i][j][1] + dp[i][k][0] * dp[k+1][j][3] ) % MOD;
            }

            // dp[i][j][2] SA型
            for(int k = i+1; k <= i + m && k < j; k ++) {
                dp[i][j][2] = ( dp[i][j][2] + dp[i][k-1][3] * dp[k][j][0] ) % MOD;
            }

            // dp[i][j][3] S型
            if(L <= m){
                int f = 1;
                for(int k = i; k <= j; k++) {
                    if(s[k] == '(' || s[k] == ')') {
                        f = 0;break;
                    }
                }
                if(f) dp[i][j][3] = 1;
            } 

            // DEBUG(i, j);
        }
    }
    cout << dp[0][n-1][0] << endl;


    return 0;
}



[CSP-S 2021] 回文

一句话题意:

你可以从a序列的两端以任意顺序取数,获得一个回文串。

关键点

  1. 数据量是 5 e 5 5e5 5e5, 因此一定是一个简单的做法。

分析

  1. 随手画一个例子,假设第一步取左端,则对应的数字必须最后取。
  2. 第二个取的数字,其对应的数字一定在倒数第二个取,所以倒数第一个和倒数第二个应该是紧挨在一起的,才可以保证这一点。
  3. 由上述的两点我们可以推定,一旦第一个数字确定下来,后面的选择是非常有限的,并不是任意可以选择的。简单论证后,证实暴力DFS复杂度仅仅是O(n)。

#include 
using namespace std;

typedef long long ll;

const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LL_INF = 0x3f3f3f3f3f3f3f3f;
const int MAXN = 5e5 + 10;
const int MAXM = 2e6 + 10;

int a[MAXN*2], b[MAXN], p[MAXN][2], vis[MAXN];
char s[MAXN*2];
int n, m, T, ans;

void DFS(int L, int R, int mL, int mR) {
    int step = n*2 - (R-L+1) + 1; // 当前选择的是第step个数字。
    if(step > n) {
        ans = 1;
        return ;
    }
    // Left
    if(L < mL) {
        if(L < mL -1 && a[L] == a[mL-1]) {
            s[step] = 'L';
            s[2*n-step + 1] = 'L';
            DFS(L+1, R, mL-1, mR);
            if(ans) return ;
        }
        if(R > mR && a[L] == a[mR+1] ) {
            s[step] = 'L';
            s[2*n-step + 1] = 'R';
            DFS(L+1, R, mL, mR+1);
            if(ans) return ;
        }
    }
    // Right
    if(R > mR) {
        if(L < mL && a[R] == a[mL-1]) {
            s[step] = 'R';
            s[2*n-step + 1] = 'L';
            DFS(L, R-1, mL-1, mR);
            if(ans) return ;
        }
        if(R > mR + 1 && a[R] == a[mR+1]) {
            s[step] = 'R';
            s[2*n-step + 1] = 'R';
            DFS(L, R-1, mL, mR+1);
            if(ans) return ;
        }
    }
}

int main(){
    cin >> T;
    while(T--) {
        cin >> n;
        memset(p, 0, sizeof(p));
        for(int i = 1; i <= 2*n; i++) {
            cin >> a[i];
            if(p[a[i]][0] == 0) p[a[i]][0] = i;
            else p[a[i]][1] = i;
        }
        ans = 0;
        s[1] = 'L';
        s[2*n] = 'L';
        DFS(2, 2*n, p[a[1]][1],  p[a[1]][1]);
        if(ans) {
            for(int i = 1; i <= 2*n; i++) {
                cout << s[i];
            }
            cout << endl;
            continue;
        }
        ans = 0;
        s[1] = 'R';
        s[2*n] = 'L';
        DFS(1, 2*n-1, p[a[2*n]][0],  p[a[2*n]][0]);
        if(ans) {
            for(int i = 1; i <= 2*n; i++) {
                cout << s[i];
            }
            cout << endl;
            continue;
        }
        cout << -1 << endl;
    }
    return 0;
}




[CSP-S 2021] 交通规划

一个写在脸上的网络流问题。最小割。

就是处理点编号的时候麻烦一点。

板子大概能得65分。其他的TLE,暂时没想到优化。

#include 
using namespace std;

typedef long long ll;

const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LL_INF = 0x3f3f3f3f3f3f3f3f;
const int MAXN = 5e5 + 10;
const int M = 2e5 + 10;

int S, T;
int n, m, K;

int mw[505][505][2];

struct Edge {
    int from,to,nxt, cap,flow;
    Edge () {}
    Edge(int from,int to,int nxt, int cap,int flow): from(from),to(to),nxt(nxt), cap(cap),flow(flow) {}
}base[MAXN<<2];
int cnt_base, head_base[MAXN];

void init() {
    cnt_base = 0;
    memset(head_base, -1, sizeof(head_base));
}
void add(int u, int v, int w) {
    base[cnt_base] = Edge(u, v, head_base[u], w, 0);
    head_base[u] = cnt_base++;
    base[cnt_base] = Edge(v, u, head_base[v], w, 0);
    head_base[v] = cnt_base++;
}



struct Dinic {
    int n,m,s,t, cnt;
    Edge edges[MAXN << 2];
    int head[MAXN];
    bool vis[MAXN];
    int d[MAXN];
    int cur[MAXN];

    void init() {
        cnt = cnt_base;
        memcpy(head, head_base, sizeof(head_base));
        memcpy(edges, base, sizeof(base));
    }
    void addedge(int from,int to,int cap) {
        edges[cnt] = Edge(from,to,head[from], cap,0);
        head[from] = cnt++;
        edges[cnt] = Edge(to,from,head[to], cap,0);
        head[to] = cnt++;
    }
    bool bfs() {
        memset(vis,0,sizeof vis);
        queue<int> q;
        q.push(s);
        vis[s] = 1;
        while(!q.empty()) {
            int now = q.front(); q.pop();
            for(int i = head[now]; ~i; i=edges[i].nxt) {
                Edge &e = edges[i];
                if(!vis[e.to] && e.cap > e.flow) {
                    vis[e.to] = 1;
                    d[e.to] = d[now] + 1;
                    q.push(e.to);
                }
            }
        }
        return vis[t];
    }
    int dfs(int x,int res) {
        if(x == t || !res) return res;
        int flow = 0,f;
        for(int &i = cur[x]; ~i; i=edges[i].nxt) {
            Edge &e = edges[i];
            if(d[x] + 1 == d[e.to] && (f = dfs(e.to,min(res,e.cap - e.flow))) > 0) {
                e.flow += f;
                edges[i^1].flow -= f;
                flow += f;
                res -= f;
                if(!res) break;
            }
        }
        return flow;
    }
    int maxflow(int s,int t) {
        this->s = s;
        this->t = t;
        int flow = 0;
        while(bfs()) {
            memcpy(cur,head,sizeof head);
            flow += dfs(s,INF);
        }
        return flow;
    }
}dinic;

int getV(int p) {
    if(p <= m) return p;
    if(p <= n+m) return (p-m) * m;
    if(p <= n+2*m) return m - (p-n-m) + 1 + (n-1)*m;
    return (n - (p-n-2*m)) * m + 1;
}
int main(){

    scanf("%d %d %d", &n, &m, &K);
    for(int i = 1; i < n; i++) for(int j = 1; j <= m; j++) scanf("%d", &mw[i][j][0]);
    for(int i = 1; i <= n; i++) for(int j = 1; j < m; j++) scanf("%d", &mw[i][j][1]);

    init();
        for(int i = 1; i < n; i++) {
            for(int j = 1; j <= m; j++) {
                int u = (i-1) * m + j;
                int v = i*m + j;
                add(u, v, mw[i][j][0]);
            }
        } 
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j < m; j++) {
                int u = (i-1) * m + j;
                int v = (i-1) * m + j + 1;
                add(u, v, mw[i][j][1]);
            }
        }
    // 以上是基础图,每次询问需要重置整个图。
    while(K--) {
        dinic.init();
        int k; scanf("%d", &k);
        T = n*m + k + 1;
        for(int i = 1; i <= k; i++) {
            int w, p, y; scanf("%d %d %d", &w, &p, &y);
            int u = n*m + i;
            int v = getV(p);
            dinic.addedge(u, v, w);
            if(y) dinic.addedge(u, T, INF);
            else dinic.addedge(0, u, INF);
        }
        printf("%d\n", dinic.maxflow(0, T));
    }


    return 0;
}



你可能感兴趣的:(CSP,csp,c++,acm竞赛,编程语言)