The 17th Zhejiang Provincial Collegiate Programming Contest 补题

题目链接

http://codeforces.com/gym/102770

参考题解

A - AD 2020

简要题意:

多组数据,每次给定起始和结束日期 yyyymmdd,询问有多少天的日期表示中含有 202 子串。

解题思路:

日期总数为几百万,不大,可以直接预处理出来,含有 202 子串的日期权值为 1,否则为 0,每次询问就是区间和。更进一步,直接存权值为 1 的日期,询问时二分确定区间长度。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
typedef long long ll;
const int maxn = 5e6 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
const int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int a[maxn];
int n;
 
inline int isLeap(int y){
 
    return y % 400 == 0 || (y % 4 == 0 && y % 100 != 0);
}
 
inline int isBad(int x){
 
    string s = to_string(x);
    return s.find("202") != string::npos;
}
 
void init(){
 
    for(int i = 2000; i <= 9999; ++i){
 
        for(int j = 1; j <= 12; ++j){
 
            int lim = days[j];
            if(isLeap(i) && j == 2) ++lim;
            for(int k = 1; k <= lim; ++k){
 
                int x = i * 10000 + j * 100 + k;
                if(isBad(x)) a[++n] = x;
            }
        }
    }
}
 
int main(){
 
    // ios::sync_with_stdio(0); cin.tie(0);
    init();
    // cout << n << endl;
    int T; scanf("%d", &T);
    while(T--){
 
        int y1, m1, d1, y2, m2, d2; 
        scanf("%d%d%d%d%d%d", &y1, &m1, &d1, &y2, &m2, &d2); 
        int x = y1 * 10000 + m1 * 100 + d1;
        int y = y2 * 10000 + m2 * 100 + d2;
        int p1 = lower_bound(a + 1, a + 1 + n, x) - a;
        int p2 = upper_bound(a + 1, a + 1 + n, y) - a;
        // cout << p1 << " " << p2 << endl;
        printf("%d\n", p2 - p1);
    }
    return 0;
}

B - Bin Packing Problem

简要题意:

给定 n n n 个物品,第 i i i 个体积为 a i a_i ai,现有容量为 C C C 的背包若干,按下标顺序存放物品,分别以 First Fit 和 Best Fit 策略进行,问分别需要多少背包。

解题思路:

使用数据结构模拟,对于 First Fit 策略,每次找到下标最小的剩余容量大于等于 a i a_i ai 的背包,由于有动态修改,使用线段树维护;对于 Best Fit 策略,每次找到最小的剩余容量大于等于 a i a_i ai 的背包,使用 multiset 进行 lower_bound 查找即可。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
const int maxn = 1e6 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
int a[maxn];
int n, C;
 
struct SegTree{
    int mx[maxn << 2];
    void pushUp(int rt){
        mx[rt] = max(mx[lson], mx[rson]);
    }
    void build(int l, int r, int rt){
        mx[rt] = C;
        if(l == r) return;
        int mid = gmid;
        build(l, mid, lson);
        build(mid + 1, r, rson);
    }
    void update(int l, int r, int rt, int val){
        if(l == r){
            mx[rt] -= val;
            return;
        }
        int mid = gmid;
        if(mx[lson] >= val) update(l, mid, lson, val);
        else update(mid + 1, r, rson, val);
        pushUp(rt);
    }
    int query(int l, int r, int rt){
        if(l == r) return mx[rt] == C ? l - 1 : l;
        int mid = gmid;
        if(mx[lson] == C) return query(l, mid, lson);
        else return query(mid + 1, r, rson);
    }
} tr;
 
int main(){
 
    // ios::sync_with_stdio(0); cin.tie(0);
    int T; scanf("%d", &T);
    while(T--){
 
        scanf("%d%d", &n, &C);
        for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
        tr.build(1, n, 1);
        for(int i = 1; i <= n; ++i){
 
            tr.update(1, n, 1, a[i]);
        }
        int ret1 = tr.query(1, n, 1);
        int ret2 = 0;
        multiset<int> st;
        for(int i = 1; i <= n; ++i){
 
            if(st.empty()){
 
                st.insert(C - a[i]);
                ++ret2;
                continue;
            }
            auto it = st.lower_bound(a[i]);
            if(it == st.end()){
 
                st.insert(C - a[i]);
                ++ret2;
                continue;
            }
            int val = *it - a[i];
            st.erase(it);
            st.insert(val);
        }
        printf("%d %d\n", ret1, ret2);
    }
    return 0;
}

C - Crossword Validation

简要题意:

给定一个 n × n n×n n×n 的方格,每个格子为 # 或小写字母,方格含有的单词为所有水平或竖直方向的极长的字符串,再给定一个字典,含有 m m m 个有权值的单词,问是否方格含有的单词都在字典出现,若是则输出方格组成的单词的总权值。

解题思路:

注意单词总长为 4 × 1 0 6 4×10^6 4×106,哈希常数太大容易超时,故使用字典树存下字典中的单词,再作对应查询。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
const int maxn = 4e6 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
char ss[1005][1005], s[maxn];
int nxt[maxn][26]; ll tag[maxn];
int n, m, cnt;
 
int add(){
 
    ++cnt; memset(nxt[cnt], 0, sizeof nxt[cnt]);
    tag[cnt] = 0; return cnt;
}
 
void init(){
 
    cnt = -1; add();
}
 
void insert(char *s, int val){
 
    int p = 0;
    while(*s){
 
        int t = *s - 'a';
        if(!nxt[p][t]) nxt[p][t] = add();
        p = nxt[p][t], ++s;
    }
    tag[p] += val;
}
 
ll query(char *s){
 
    int p = 0;
    while(*s){
 
        int t = *s - 'a';
        if(!nxt[p][t]) return 0;
        p = nxt[p][t], ++s;
    }
    return tag[p];
}
 
int main(){
 
    // ios::sync_with_stdio(0); cin.tie(0);
    int T; scanf("%d", &T);
    while(T--){
 
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; ++i) scanf("%s", ss[i] + 1);
        init();
        ll ret = 0;
        for(int i = 1; i <= m; ++i){
 
            int val; scanf("%s%d", s, &val);
            insert(s, val);
        }
        for(int i = 1; i <= n; ++i){
 
            for(int j = 1; j <= n; ++j){
 
                if(ss[i][j] == '#') continue;
                int p = 0;
                while(j <= n && ss[i][j] != '#') s[p++] = ss[i][j], ++j;
                s[p] = 0;
                ll tmp = query(s);
                if(!tmp) { ret = -1; break; }
                ret += tmp;
            }
            if(ret == -1) break;
            for(int j = 1; j <= n; ++j){
 
                if(ss[j][i] == '#') continue;
                int p = 0;
                while(j <= n && ss[j][i] != '#') s[p++] = ss[j][i], ++j;
                s[p] = 0;
                ll tmp = query(s);
                if(!tmp) { ret = -1; break; }
                ret += tmp;
            }
            if(ret == -1) break;
        }
        printf("%lld\n", ret);
    }
    return 0;
}

E - Easy DP Problem

简要题意:

给一个二维 DP 的转移方程,多次询问某个 d p dp dp 值。转化后子问题为:每次询问区间的前 k k k 大的数的和。

解题思路:

转化的参考思路:将 d p dp dp 状态画成网格,询问 d p i , j dp_{i, j} dpi,j 的值,从转移方向来看,有 d p i − 1 , j dp_{i - 1, j} dpi1,j d p i − 1 , j − 1 + b i dp_{i - 1, j - 1} + b_i dpi1,j1+bi,每次 i i i 递减 1 1 1 并获得权值 i 2 i^2 i2,这部分是固定的,为 ∑ k = 1 i k 2 \sum\limits_{k = 1}^{i} k^2 k=1ik2。取 m a x max max 的部分,考虑从 ( i , j ) (i, j) (i,j) 走到 ( 0 , 0 ) (0, 0) (0,0),每次在 j j j 方向走则获得权值 b i b_i bi,故这部分最大值为 b i b_i bi 的前 j j j 大之和。

维护部分则用主席树,获取区间后,求靠右的 k k k 个数之和。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
const int maxn = 5e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
int xi[maxn], cntX;
int a[maxn], num[maxn * 40], ls[maxn * 40], rs[maxn * 40], rt[maxn];
ll sum_p[maxn], sum[maxn * 40];
int n, q, tot;
 
void update(int l, int r, int &rt, int pre, int pos){
 
    rt = ++tot; num[rt] = num[pre] + 1, sum[rt] = sum[pre] + xi[pos], ls[rt] = ls[pre], rs[rt] = rs[pre];
    if(l == r) return;
    int mid = gmid;
    if(pos <= mid) update(l, mid, ls[rt], ls[pre], pos);
    else update(mid + 1, r, rs[rt], rs[pre], pos);
}
 
ll query(int l, int r, int rt, int pre, int k){
 
    if(l == r) return k * 1ll * xi[l];
    int mid = gmid, d = num[rs[rt]] - num[rs[pre]];
    if(d >= k) return query(mid + 1, r, rs[rt], rs[pre], k);
    else return sum[rs[rt]] - sum[rs[pre]] + query(l, mid, ls[rt], ls[pre], k - d);
}
 
ll solve(int l, int r, int k){
 
    ll ret = query(1, cntX, rt[r], rt[l - 1], k);
    // cout << ret << " yyy" << endl;
    return ret + sum_p[r - l + 1];
}
 
int main(){
 
    // ios::sync_with_stdio(0); cin.tie(0);
    sum_p[0] = 0;
    for(int i = 1; i < maxn; ++i) sum_p[i] = sum_p[i - 1] + i * 1ll * i;
    int T; scanf("%d", &T);
    while(T--){
 
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
        tot = cntX = 0;
        for(int i = 1; i <= n; ++i){
 
            xi[++cntX] = a[i];
            rt[i] = 0;
        }
        sort(xi + 1, xi + 1 + cntX);
        cntX = unique(xi + 1, xi + 1 + cntX) - xi - 1;
        for(int i = 1; i <= n; ++i){
 
            int x = lower_bound(xi + 1, xi + 1 + cntX, a[i]) - xi;
            update(1, cntX, rt[i], rt[i - 1], x);
        }
        scanf("%d", &q);
        while(q--){
 
            int l, r, k; scanf("%d%d%d", &l, &r, &k);
            ll ret = solve(l, r, k);
            printf("%lld\n", ret);
        }
    }
    return 0;
}

F - Finding a Sample

简要题意:

给定两个 n n n 维样本的二元分类器,参数为 ( w 1 , b 1 ) (\bold{w_1}, b_1) (w1,b1) ( w 2 , b 2 ) (\bold{w_2}, b_2) (w2,b2),构造 x \bold{x} x 使得 ( w 1 T x + b 1 ) ( w 2 T x + b 2 ) < 0 (\bold{w_1^Tx} + b_1)(\bold{w_2^Tx} + b_2) < 0 (w1Tx+b1)(w2Tx+b2)<0。无解输出 No。

解题思路:

无解当且仅当分类器对应的两个超平面重合,即对应法向量 w \bold{w} w 同向且偏置 b b b 成对应比例,否则必定有解。对应解的构造,分类讨论即可,只需要两维来构造。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
const int maxn = 3e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
#define eps 1e-7
const double lim = 1e4;
int a[maxn], b[maxn];
double xi[maxn];
int n;
 
int solve(){
 
    for(int i = 1; i <= n; ++i) xi[i] = 0;
    if(a[0] * b[0] < 0) return 1;
    auto check = [](int ai, int a0, int bi, int b0, double &x) -> int{
        auto getX = [](int ai, int a0) -> vector<double>{
            vector<double> ax;
            if(!ai) ax.pb(0);
            else ax.pb(-1.0 * a0 / ai + eps), ax.pb(-1.0 * a0 / ai - eps);
            return ax;
        };
        auto ax = getX(ai, a0), bx = getX(bi, b0);
        auto cal = [](int ai, int a0, double x) -> double{
            return ai * x + a0;
        };
        for(auto &v : ax) if(cal(ai, a0, v) * cal(bi, b0, v) < 0) { x = v; return 1; }
        for(auto &v : bx) if(cal(ai, a0, v) * cal(bi, b0, v) < 0) { x = v; return 1; }
        return 0;
    };
    for(int i = 1; i <= n; ++i) if(check(a[i], a[0], b[i], b[0], xi[i])) return 1;
    if(!a[0] && !b[0]){
        for(int i = 1; i <= n; ++i){
            for(int j = 1; j <= n; ++j){
                if(check(a[i], a[j], b[i], b[j], xi[i])) { xi[j] = 1; return 1; }
            }
        }
    }
    return 0;
}
 
int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    cout << fixed << setprecision(8);
    int T; cin >> T;
    while(T--){
 
        cin >> n;
        for(int i = 1; i <= n; ++i) cin >> a[i]; cin >> a[0];
        for(int i = 1; i <= n; ++i) cin >> b[i]; cin >> b[0];
        if(!solve()) cout << "No" << "\n";
        else{
 
            for(int i = 1; i <= n; ++i) cout << xi[i] << " \n"[i == n];
        }
    }
    return 0;
}

G - Gliding

简要题意:

三维空间里,给定起点 ( s x , s y , 0 ) (sx, sy, 0) (sx,sy,0) 和终点 ( t x , t y , 0 ) (tx, ty, 0) (tx,ty,0),水平速度为 v h v_h vh,竖直下落速度为 v f v_f vf,开滑翔伞后为 v p ( v p < v f ) v_p(v_p < v_f) vp(vp<vf)。同时有 n + 1 n + 1 n+1 个风洞 ( x i , y i , 0 ) (x_i, y_i, 0) (xi,yi,0),提供 v i v_i vi 的上升速度。 起点落在第 0 0 0 个风洞 ( v 0 > v p ) (v_0 > v_p) (v0>vp),问从起点到终点的最短时间。

解题思路:

首先滑翔伞一直开最优,其次 v i ≤ v p v_i \leq v_p vivp 的风洞没用。从起点到终点,依次使用的风洞的 v v v 递增(否则可以原地上升,无需使用 v v v 更小的风洞)。从终点反向考虑,按 v v v 建立拓扑图, D P DP DP 最短路即可。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
const int maxn = 4e3 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
#define eps 1e-11
const double oo = 1e20;
struct Node{
    double x, y, v;
    bool operator < (const Node &o) const{
        return v < o.v;
    }
} a[maxn];
struct Edge{
    double w; int v;
};
vector<Edge> G[maxn];
double dp[maxn], vf, vp, vh;
int n, sx, sy, tx, ty;
 
#define sqr(x) ((x)*(x))
inline double calDis(int i, int j){
 
    return sqrt(sqr(a[i].x - a[j].x) + sqr(a[i].y - a[j].y)) / vh;
}
 
inline int getS(){
 
    for(int i = 0; i < n; ++i) if(a[i].x == sx && a[i].y == sy) return i;
    return 0;
}
 
void build(){
 
    for(int i = 0; i <= n; ++i) G[i].clear();
    sort(a, a + n);
    for(int i = n; i >= 1; --i){
 
        if(a[i].v < vp) break;
        for(int j = i - 1; j >= 0; --j){
 
            if(a[j].v < vp) break;
            double w = calDis(i, j);
            w += w * vp / (a[j].v - vp);
            // cout << i << " -> " << j << " " << w << endl;
            G[i].pb(Edge{w, j});
        }
    }
}
 
double solve(){
 
    build();
    int sp = getS();
    for(int i = 0; i <= n; ++i) dp[i] = oo;
    dp[n] = 0;
    for(int i = n; i >= 1; --i){
 
        for(auto &e : G[i]){
 
            dp[e.v] = min(dp[e.v], dp[i] + e.w);
        }
    }
    return dp[sp];
}
 
int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    cout << fixed << setprecision(11);
    int T; cin >> T;
    while(T--){
 
        cin >> sx >> sy >> tx >> ty;
        cin >> vf >> vp >> vh >> n;
        for(int i = 0; i <= n; ++i){
 
            cin >> a[i].x >> a[i].y >> a[i].v;
        }
        a[++n] = {tx, ty, 23333333};
        cout << solve() << "\n";
    }
    return 0;
}

I - Invoking the Magic

简要题意:

给定 n n n 对袜子,开始不一定是配对的。每次魔法操作可以选择 k k k 对袜子进行两两自动匹配(必须完美匹配),问能将全部袜子匹配的最小 k k k 值。每种颜色的袜子有且仅有一对。

解题思路:

颜色范围很大,先进行离散化。以颜色为结点,初始配对状态为边建图。由于每种颜色袜子只有一对,每个点度数都为 2 2 2,那么每次魔法操作选择为一个连通块(环)。建图后求最大的连通块大小即可。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
const int maxn = 3e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
vector<int> G[maxn];
int xi[maxn], cntX;
int a[maxn], b[maxn], vis[maxn];
int n, tot;
 
void dfs(int u){
 
    ++tot, vis[u] = 1;
    for(auto &v : G[u]){
 
        if(!vis[v]) dfs(v);
    }
}
 
int main(){
 
    // ios::sync_with_stdio(0); cin.tie(0);
    int T; scanf("%d", &T);
    while(T--){
 
        scanf("%d", &n);
        cntX = 0;
        for(int i = 1; i <= n; ++i){
 
            scanf("%d%d", &a[i], &b[i]);
            xi[++cntX] = a[i];
            xi[++cntX] = b[i];
        }
        sort(xi + 1, xi + 1 + cntX);
        cntX = unique(xi + 1, xi + 1 + cntX) - xi - 1;
        for(int i = 1; i <= cntX; ++i) G[i].clear();
        for(int i = 1; i <= n; ++i){
 
            a[i] = lower_bound(xi + 1, xi + 1 + cntX, a[i]) - xi;
            b[i] = lower_bound(xi + 1, xi + 1 + cntX, b[i]) - xi;
            G[a[i]].pb(b[i]), G[b[i]].pb(a[i]);
        }
        for(int i = 1; i <= cntX; ++i) vis[i] = 0;
        int ret = 0;
        for(int i = 1; i <= cntX; ++i){
 
            if(!vis[i]) tot = 0, dfs(i), ret = max(ret, tot);
        }
        printf("%d\n", ret);
    }
    return 0;
}

K - Killing the Brute-force

简要题意:

签到题。

解题思路:

签到题。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
typedef long long ll;
const int maxn = 3e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
int a[maxn], b[maxn];
int n;
 
int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    int T; cin >> T;
    while(T--){
 
        cin >> n;
        for(int i = 1; i <= n; ++i) cin >> a[i];
        for(int i = 1; i <= n; ++i) cin >> b[i];
        int ret = -1;
        for(int i = 1; i <= n; ++i){
 
            if(b[i] > 3 * a[i]) { ret = i; break; }
        }
        cout << ret << endl;
    }
    return 0;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值