2022杭电多校 第一场 个人题解(ABCDIHK)

A:string

题意:

给定一个字符串 s s s k k k,求 s s s的每个前缀子串的所有满足 ( l e n ∗ 2 − i ) % k = = 0 (len*2-i)\%k==0 (len2i)%k==0的所有 b o a r d board board的数量。( l e n len len b o a r d board board的长度)。

分析:

既然跟 b o a r d board board有关,那么很容易想到先对 s s s建个 b o a r d board board树,在去思考如何解决问题。观察等式,也就是 2 ∗ l e n ≡ i ( m o d k ) 2*len\equiv i(modk) 2leni(modk) ,然后我们开个vector cnt[N]记录长度为 l e n ∗ 2 % k len*2\%k len2%k b o a r d board board的数量。每遍历一个点加进去即可:cnt[2 * u % k].push_back(2 * u),即可。
那么如何统计答案?我们的答案数量是在当前的树上从根节点到当前结点的一条链上的个数,同时需满足长度 2 ∗ l e n > u 2*len>u 2len>u.我们利用二分查找即可。细节看代码。
但是因为杭电的sb评测机,我们递归1e6层也会爆栈。。。。

code:

typedef long long LL;
typedef pair<int, int> pii;
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
} 
template <typename T> void inline pr(T x) {
    if (x < 0) { putchar('-'); pr(-x); return ; }
    if (x >= 10) pr(x / 10);
    putchar((x % 10) + '0');
}
inline LL ksm(LL a, LL b, int mod){
    LL ans = 1; 
    for(; b; b >>= 1, a = a * a % mod) if(b & 1) ans = ans * a % mod;
    return ans;
}
const int N = 1e6 + 10, mod = 998244353;

char s[N];
int ne[N];
LL ans[N];
int k;
vi cnt[N];
vi edge[N];

void dfs(int u) {
    int val = 2 * u % k;
    cnt[val].pb(2 * u);
    int x = u % k;
    if(cnt[x].size()) {
        ans[u] = cnt[x].size() - (upper_bound(all(cnt[x]), u) - cnt[x].begin());
    } else ans[u] = 0;
    for(auto t : edge[u]) dfs(t);
    cnt[val].pop_back();
}
void solve() {
    scanf("%s%d", s + 1, &k);
    int n = strlen(s + 1);
    for(int i = 0; i <= n; i++) ne[i] = 0, edge[i].clear();
    for(int i = 2, j = 0; i <= n; i++) {
        while(j > 0 && s[i] != s[j + 1]) j = ne[j];
        if(s[i] == s[j + 1]) j++;
        ne[i] = j;
        edge[j].pb(i);
    }
    edge[0].pb(1);
    dfs(0);
    LL res = 1;
    for(int i = 1; i <= n; i++) {
        res = (ans[i] + 1) * res % mod;
        ans[i] = 0;
    }
    cout << res << '\n';
}
signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif

    int T = 1;
    read(T);
    while(T--) {
        solve();
    }
    exit(0);
}

B:Dragon slayer

题意:

给定一个起点和终点,有些放个之间存在一些墙不能通过,问至少要删去几道墙可以到达终点。

分析:

读完题发现墙的数量最多只有15个,我们直接二进制枚举爆搜,当然 b f s bfs bfs也可以过,但是这题卡常,写 b f s bfs bfs的时候搜到终点就直接终止。时间复杂度 O ( n ∗ m ∗ 2 15 ) O(n*m*2^{15}) O(nm215)

code:

typedef long long LL;
typedef pair<int, int> pii;
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
} 
template <typename T> void inline pr(T x) {
    if (x < 0) { putchar('-'); pr(-x); return ; }
    if (x >= 10) pr(x / 10);
    putchar((x % 10) + '0');
}
inline LL ksm(LL a, LL b, int mod){
    LL ans = 1; 
    for(; b; b >>= 1, a = a * a % mod) if(b & 1) ans = ans * a % mod;
    return ans;
}
const int N = 1 << 16;

int n, m, k, sx, sy, tx, ty;
bool f[N];
bool vis[20][20], L[20][20], R[20][20];
struct Node {
    int x1, y1, x2, y2;
}p[20];

bool dfs(int x, int y) {
    vis[x][y] = 1;
    if(x == tx && y == ty) return 1;
    bool q = 0;
    if(x - 1 >= 0 && !L[x][y] && !vis[x - 1][y])
        q |= dfs(x - 1, y);
    if(x + 1 < n && !L[x + 1][y] && !vis[x + 1][y])
        q |= dfs(x + 1, y);
    if(y >= 1 && !R[x][y] && !vis[x][y - 1])
        q |= dfs(x, y - 1);
    if(y + 1 < m && !R[x][y + 1] && !vis[x][y + 1])
        q |= dfs(x, y + 1);
    return q;
}
bool check(int x) {
    memset(R, 0, sizeof R);
    memset(L, 0, sizeof L);
    memset(vis, 0, sizeof vis);
    for(int i = 0; i < k; i++) {
        if(x >> i & 1) continue;
        if(p[i].x1 == p[i].x2) { //竖墙
            for(int j = p[i].y1; j < p[i].y2; j++)
                L[p[i].x1][j] = 1;
        }
        if(p[i].y1 == p[i].y2) { //横墙
            for(int j = p[i].x1; j < p[i].x2; j++)
                R[j][p[i].y1] = 1;
        }
    }
    return dfs(sx, sy);
}
void solve() {
    read(n), read(m), read(k), read(sx), read(sy), read(tx), read(ty);
    for(int i = 0; i < k; i++) {
        int a, b, c, d;
        read(a), read(b), read(c), read(d);
        if(a == c && b > d) swap(b, d);
        if(b == d && a > c) swap(a, c);
        p[i] = {a, b, c, d}; 
    }
    for(int i = 0; i < (1 << k); i++) f[i] = 0;
    int ans = k;
    for(int i = 0; i < (1 << k); i++) {
        if(f[i]) continue;
        f[i] = check(i);
 
        if(f[i]) {
            ans = min(ans, __builtin_popcount(i));
            for(int j = 0; j < k; j++) {
                f[i | (1 << j)] |= f[i];
            }
        }
    }
    cout << ans << '\n';
}
signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
    // auto now = clock();
#endif
    int T;
    read(T);
    while(T--) {
        solve();
    }
}

C:Backpack

题意:

n个物品,背包容量为m,每个物品有价值和体积,询问恰好能把背包装满的所选择物品的最大价值异或和。

分析:

解法一:

读完题一眼背包: f [ i , j , k ] 表示前 i 个物品选择总体积为 j ,且价值异或和为 k 的方案能否取到 f[i,j,k]表示前i个物品选择总体积为j,且价值异或和为k的方案能否取到 f[i,j,k]表示前i个物品选择总体积为j,且价值异或和为k的方案能否取到
状态转移方程也很好想:
f [ i , j , k ] = f [ i − 1 , j , k ] ∣ f [ i − 1 , j − v , k 异或 w ] f[i,j,k]=f[i-1,j,k] | f[i-1,j-v,k异或w] f[i,j,k]=f[i1,j,k]f[i1,jv,k异或w].
但是这题内存限制,三维肯定是开不下的,那么观察到我们要的只是0,1,那么我们可以用 b i t s e t bitset bitset压位优化。bitset p[i] p [ i ] [ j ] p[i][j] p[i][j]表示异或和为 i i i,总体积为 j j j的方案能否取到,那么我们选一个物品相当于体积左移w.

code:

typedef long long LL;
typedef pair<int, int> pii;
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
} 
template <typename T> void inline pr(T x) {
    if (x < 0) { putchar('-'); pr(-x); return ; }
    if (x >= 10) pr(x / 10);
    putchar((x % 10) + '0');
}
inline LL ksm(LL a, LL b, int mod){
    LL ans = 1; 
    for(; b; b >>= 1, a = a * a % mod) if(b & 1) ans = ans * a % mod;
    return ans;
}
const int N = 1 << 16;

bitset<1050> f[1050], g[1050];
void solve() {
    int n, m;
    cin >> n >> m;
    for(int i = 0; i < 1024; i++) f[i].reset();
    f[0][0] = 1;
    for(int i = 1; i <= n; i++) {
        int v, w;
        cin >> v >> w;
        for(int j = 0; j < 1024; j++) {
            g[j] = f[j];
            g[j] <<= v;
        }
        for(int j = 0; j < 1024; j++) {
            f[j] |= g[j ^ w];
        }
    }
    int ans = -1;
    for(int i = 0; i < 1024; i++) {
        if(f[i][m]) {
            ans = i;
        }
    }
    cout << ans << '\n';
}
signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
    // auto now = clock();
#endif
    int T;
    read(T);
    while(T--) {
        solve();
    }
}

解法二:

比赛的时候并不怎么会用bitset ,以为是个数据结构优化dp,然后因为是异或,所以想到了字典树上求最大异或路径,那么就可以对每一个背包体积建一个字典树维护即可。每一个背包容量为 j j j的所选的物品方案就成了m棵字典树,然后寻求最大值即可。

typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> pii;
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
} 
template <typename T> void inline pr(T x) {
    if (x < 0) { putchar('-'); pr(-x); return ; }
    if (x >= 10) pr(x / 10);
    putchar((x % 10) + '0');
}

const int N = 2e3 + 10, M = N * 2, mod = 1e9 + 7;
inline LL ksm(LL a, LL b){
    LL ans = 1; 
    for(; b; b >>= 1, a = a * a % mod) if(b & 1) ans = ans * a % mod;
    return ans;
}
//----------------------------------------------------------------------------------------//
int f[1100], tr[2100][1100 * 2][2];
int tot;
int idx[N];
void insert(int u, int w) {
    int p = 0;
    for(int i = 9; i >= 0; i--) {
        int c = w >> i & 1;
        if(!tr[u][p][c]) tr[u][p][c] = ++idx[u];
        p = tr[u][p][c];
    }
}
int ask(int u, int w) {
    int p = 0;
    int res = 0;
    for(int j = 9; j >= 0; j--) {
        int c = w >> j & 1;
        if(tr[u][p][!c]) {
            res += 1 << j, p = tr[u][p][!c];
        } else {
            p = tr[u][p][c];
        }
    }
    return res;
}
void solve() {
    memset(f, -1, sizeof f);
    memset(tr, 0, sizeof tr);
    memset(idx, 0, sizeof idx);
    f[0] = 0;
    tot = 0;
    int n, m;
    read(n), read(m);
    insert(0, 0);
    for(int i = 1; i <= n; i++) {
        int v, w;
        read(v), read(w);
        for(int j = m; j >= v; j--) {
            if(f[j - v] == -1) continue;
            int val = ask(j - v, w);
            f[j] = max(f[j], val);
            insert(j, val);
        }
    }
    cout << f[m] << '\n';
}
signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
    // auto now = clock();
#endif
    int T;
    read(T);
    while(T--) {
        solve();
    }    

// #ifdef JANGYI

//     cout << "================================" << endl;
//     cout << "Program run for " << (clock() - now) / (double)CLOCKS_PER_SEC * 1000 << " ms." << endl;
// #endif
    return 0;
}   

H:Path

题意:

给定一个有向带权图,边分为特殊边和普通边。假如 u − > v u->v u>v之间的边是特殊边,那么下一步 v v v可以选择任意一个与 v v v没有边相连的点花费0转移过去,或者选择走与 v v v想连的边走过去,但是边权变为 w [ i ] − k w[i] - k w[i]k。询问每个点到其实点的最短路。

分析:

如果没有特殊边就是一个普通的最短路。加入了特殊边我们很容易想到分层图,但是这个分层图因为空间太大是建不起来的。求最短路用的是 b f s bfs bfs,那么我们先把当前所有为被遍历到的点放到集合 s e t set set里,遍历该顶点那么一定就是最短路了,把该点删去即可。对于普通边,我们直接删去即可。对于特殊边呢,我们先把与其相连的不能花费0转移的点标记出来,然后对于其它在 s e t set set里的点我们从当前点直接花费0转移过去即可,对于相邻的点呢?我们在最短路更新过程中直接减去 k k k即可。
因此,我们定义 d i s t [ i ] [ 0 ] , d i s t [ i ] [ 1 ] dist[i][0],dist[i][1] dist[i][0],dist[i][1],分别表示到 i i i的上一条边是普通/特殊边。

code:

typedef long long LL;
typedef pair<int, int> pii;
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
} 
template <typename T> void inline pr(T x) {
    if (x < 0) { putchar('-'); pr(-x); return ; }
    if (x >= 10) pr(x / 10);
    putchar((x % 10) + '0');
}
inline LL ksm(LL a, LL b, int mod){
    LL ans = 1; 
    for(; b; b >>= 1, a = a * a % mod) if(b & 1) ans = ans * a % mod;
    return ans;
}
const int N = 1e6 + 10, mod = 998244353;
#define int LL
int h[N], e[N << 1], ne[N << 1], type[N << 1], w[N << 1], idx;
int n, m, s, k;
void add(int a, int b, int c, int d) {
    e[idx] = b; ne[idx] = h[a]; w[idx] = c; type[idx] = d; h[a] = idx++;
}
struct Node {
    int a, d, type_;
    bool operator < (const Node &w) const {
        return d > w.d;
    }
};
int dist[N][2], g[N];
bool st[N][2];
void bfs() {
    for(int i = 0; i <= n; i++) dist[i][0] = dist[i][1] = INF, st[i][0] = st[i][1] = 0;
    priority_queue<Node> q;
    dist[s][0] = 0;
    q.push({s, 0, 0});
    set<int> S;
    for(int i = 1; i <= n; i++) {
        if(i != s) S.insert(i);
    }
    int cnt = 0;
    while(q.size()) {
        auto now = q.top(); q.pop();
        if(now.type_ == 0) { //上一个经过的是普通边
            S.erase(now.a);
        } else {
            ++cnt;
            //标记下一个相邻的顶点
            for(int i = h[now.a]; ~i; i = ne[i]) {
                int j = e[i];
                g[j] = cnt;
            }
            vi temp;
            for(auto it : S) {
                if(g[it] != cnt) temp.pb(it);
            }
            for(auto t : temp) {
                dist[t][0] = dist[now.a][now.type_];
                q.push({t, dist[t][0], 0});
            }
            for(auto t : temp) S.erase(t);
        }
        int y = 0;
        if(now.type_) y -= k;
        if(st[now.a][now.type_]) continue;
        st[now.a][now.type_] = 1;
        for(int i = h[now.a]; ~i; i = ne[i]) {
            int j = e[i];
            if(dist[j][type[i]] > dist[now.a][now.type_] + w[i] + y) {
                dist[j][type[i]] = dist[now.a][now.type_] + w[i] + y;
                q.push({j, dist[j][type[i]], type[i]});
            }
        }
    }
}
void solve() {
    read(n), read(m), read(s), read(k);
    idx = 0;
    for(int i = 0; i <= n; i++) h[i] = -1, g[i] = 0;
    for(int i = 1; i <= m; i++) {
        int a, b, c, d;
        read(a), read(b), read(c), read(d);
        add(a, b, c, d);
    }
    bfs();
    for(int i = 1; i <= n; i++) {
        if(min(dist[i][0], dist[i][1]) == INF) cout << -1 << ' ';
        else cout << min(dist[i][0], dist[i][1]) << ' ';
    }
    cout << '\n';
}

#define INF 0x3f3f3f3f3f3f3f3f
#define inf 0x3f3f3f3f
#define lowbit(x) x & -x
#define sqr(x) ((x) * (x))
#define pb push_back
#define se second
#define fi first
#define endl '\n'
#define all(x) (x).begin(), (x).end()
#define vi vector<int>
#define vl vector<long long>
#define vii vector<pair<int, int>>
#define D(x) cout << "BUG:  " << (x) << '\n'
#define DD(x, y) cout << "BUG:  " << (x) << "  " << (y) << '\n'
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> pii;
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
} 
template <typename T> void inline pr(T x) {
    if (x < 0) { putchar('-'); pr(-x); return ; }
    if (x >= 10) pr(x / 10);
    putchar((x % 10) + '0');
}
inline LL ksm(LL a, LL b, int mod){
    LL ans = 1; 
    for(; b; b >>= 1, a = a * a % mod) if(b & 1) ans = ans * a % mod;
    return ans;
}
const int N = 1e6 + 10, mod = 998244353;
#define int LL
int h[N], e[N << 1], ne[N << 1], type[N << 1], w[N << 1], idx;
int n, m, s, k;
void add(int a, int b, int c, int d) {
    e[idx] = b; ne[idx] = h[a]; w[idx] = c; type[idx] = d; h[a] = idx++;
}
struct Node {
    int a, d, type_;
    bool operator < (const Node &w) const {
        return d > w.d;
    }
};
int dist[N][2], g[N];
bool st[N][2];
void bfs() {
    for(int i = 0; i <= n; i++) dist[i][0] = dist[i][1] = INF, st[i][0] = st[i][1] = 0;
    priority_queue<Node> q;
    dist[s][0] = 0;
    q.push({s, 0, 0});
    set<int> S;
    for(int i = 1; i <= n; i++) {
        if(i != s) S.insert(i);
    }
    int cnt = 0;
    while(q.size()) {
        auto now = q.top(); q.pop();
        if(now.type_ == 0) { //上一个经过的是普通边
            S.erase(now.a);
        } else {
            ++cnt;
            //标记下一个相邻的顶点
            for(int i = h[now.a]; ~i; i = ne[i]) {
                int j = e[i];
                g[j] = cnt;
            }
            vi temp;
            for(auto it : S) {
                if(g[it] != cnt) temp.pb(it);
            }
            for(auto t : temp) {
                dist[t][0] = dist[now.a][now.type_];
                q.push({t, dist[t][0], 0});
            }
            for(auto t : temp) S.erase(t);
        }
        int y = 0;
        if(now.type_) y -= k;
        if(st[now.a][now.type_]) continue;
        st[now.a][now.type_] = 1;
        for(int i = h[now.a]; ~i; i = ne[i]) {
            int j = e[i];
            if(dist[j][type[i]] > dist[now.a][now.type_] + w[i] + y) {
                dist[j][type[i]] = dist[now.a][now.type_] + w[i] + y;
                q.push({j, dist[j][type[i]], type[i]});
            }
        }
    }
}
void solve() {
    read(n), read(m), read(s), read(k);
    idx = 0;
    for(int i = 0; i <= n; i++) h[i] = -1, g[i] = 0;
    for(int i = 1; i <= m; i++) {
        int a, b, c, d;
        read(a), read(b), read(c), read(d);
        add(a, b, c, d);
    }
    bfs();
    for(int i = 1; i <= n; i++) {
        if(min(dist[i][0], dist[i][1]) == INF) cout << -1 << ' ';
        else cout << min(dist[i][0], dist[i][1]) << ' ';
    }
    cout << '\n';
}

signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    int T;
    read(T);
    while(T--) {
        solve();
    }
    return 0;
}

你可能感兴趣的:(深度优先,图论,算法)