D1T1 小凯的疑惑

给两个互素的数aa,bb。求满足条件最大的整数nn使得aa,bb无法通过非负系数线性组合出nn。即

\nexists x, y \in \mathbb N \text{ sat. } 
ax + by = n

数据范围a, b \leq 10^9a,b109

算法1

考虑dp。

dp[x] = dp[x - a] \text{ or } dp[x - b]dp[x]=dp[xa] or dp[xb]

初始边界dp[0] = \text{true}dp[0]=true。如果连续的一段长度a都有dp值为true,则说明已经不可能再有拼不到的了。

时间复杂度:\Theta(ab)Θ(ab)

得人60\%60%。

算法2

n=abn=ab向下遍历。通过扩展欧几里得得到ax + by = 1ax+by=1的解。则有-ax - by = -1axby=1。一点点向下搜索。

时间复杂度:\Theta(a + b)Θ(a+b)

将来会给出解释。得分60\%60%。

算法2

不妨用算法1的暴力来测些数据找规律。

输入:

10007 10009

输出:

100140047

这个数好像还蛮整的呢!最后验证发现是ab - a - babab。

与算法1对拍,发现完全ok!

正确性证明:不会证orz

时间复杂度:\Theta(1)Θ(1),预计得分100\%100%。

样例代码

#include <cstdio>

using namespace std;

int main() {
    long long a, b;
    scanf("%lld%lld", &a, &b);
    printf("%lld\n", a * b - a - b);
    return 0;
}

D1T2 时间复杂度

有一种只有循环语句的语言:

F i x y
...
E

i是命名的变量,x和y只可能是1~100的整数或极大参量nn。询问整个程序运行的时间复杂度与输入给定的是否一致。如果循环嵌套内变量命名重复或者循环不匹配,输出编译错误提示ERR

<del>算法1</del>

这道题有个p的算法?

其实就是糟心输入+拿栈暴力模拟+各种糟心分类讨论。

样例代码

#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#include <algorithm>
#include <functional>
#include <stack>

#define LOG(FMT...) // if (t == 6) fprintf(stderr, FMT)

using namespace std;

char s[100];
bool used[256];

int main() {
    int t, l, inc, c1, c2, c3, p;
    char cur;
    bool flag;
    scanf("%d", &t);
    while (t--) {
        scanf("%d%s", &l, s);
        if (s[2] == '1')
            inc = 0;
        else
            sscanf(s + 4, "%d", &inc);
        flag = false;
        gets(s);
        stack<int> cp, cp2;
        stack<char> prm;
        cp2.push(0);
        memset(used, 0, sizeof(used));
        while (l--) {
            // LOG("READ\n");
            gets(s);
            if (flag)
                continue;
            if (s[0] == 'E') {
                if (prm.empty()) {
                    flag = true;
                    continue;
                }
                c1 = cp.top();
                cp.pop();
                c3 = cp2.top();
                cp2.pop();
                c2 = cp2.top();
                cp2.pop();
                LOG("E: %d %d %d\n", c1, c2, c3);
                if (c3 == -1)
                    c3 = 0;
                if (c1 != -1)
                    c1 += c3;
                if (c1 == -1)
                    cp2.push(c2); // ei died at this line
                else
                    cp2.push(max(c1, c2));
                used[prm.top()] = false;
                prm.pop();
                continue;
            }
            if (used[s[2]]) {
                flag = true;
                continue;
            }
            used[s[2]] = true;
            prm.push(s[2]);
            p = 4;
            if (s[p] == 'n') {
                c1 = 1000;
                p += 2;
            } else {
                sscanf(s + p, "%d", &c1);
                if (c1 >= 100)
                    ++p;
                if (c1 >= 10)
                    ++p;
                ++p;
            }
            while (s[p] == ' ')
                ++p;
            if (s[p] == 'n')
                c2 = 1000;
            else
                sscanf(s + p, "%d", &c2);
            cp2.push(-1);
            LOG("F: %d %d\n", c1, c2);
            if (c1 > c2)
                cp.push(-1);
            else if (c2 == 1000 && c1 != 1000)
                cp.push(1);
            else
                cp.push(0);
        }
        c1 = cp2.top();
        if (c1 == -1)
            c1 = 0;
        if (!prm.empty())
            flag = true;
        if (flag)
            puts("ERR");
        else
            puts(c1 == inc ? "Yes" : "No");
        LOG("%d %d\n", cp2.top(), inc);
    }
    return 0;
}

D1T3 逛公园

给一个非负权有向图,求其中从1到nn的路径(可以不是简单路径)中长度与最短路差距不超过kk的。如果有无数条,特别地输出-1。

数据范围n \leq 10^5, m \leq 2 \times 10^5, k \leq 50n105,m2×105,k50

算法1

回顾一下最短路计数问题。

根据三角不等式,正权回路上,两点之间的最短路径必然无环,所以从原点ss走出的最短路径构成一个SPDAG(Shortest Path DAG)。

在这种情况下,可以先跑一遍最短路,处理出ss到每个点的最短距离dis[u]dis[u]。可写出对边是否在最短路上的判定

dis[v] = dis[u] + w(u, v)dis[v]=dis[u]+w(u,v)

故可列关于当前节点的状态转移方程

```cpp dp[u] = \left\{ \begin{array}{rl} 1 & \text{if} & u = t \\ \sum_{(u,v)\in G, dis[v] = dis[u] + w(u, v)} dp[v] & \text{else} \end{array} ``` \right.cppdp[u]={1(u,v)G,dis[v]=dis[u]+w(u,v)dp[v]ifelseu=t

可过无0权且k=0k=0的点。

时间复杂度\Theta(m\log m) + \Theta(m) = \Theta(m \log m)Θ(mlogm)+Θ(m)=Θ(mlogm)

预计得分15\%15%。

算法2

考虑无0权的情况。

因为三角不等式依旧成立,可知不可能存在无穷条符合条件的路径。

考虑对原图分层,令dp[u][j]dp[u][j]表示从ss到uu比最短路径已经多走了jj个单位长度的情况下,满足条件的路径数。可列状态转移方程

```cpp dp[u][j] = \left\{ \begin{array}{rl} 1 & \text{if} & u = t \\ 0 & \text{else} \end{array} ``` \right. + \sum\_{(u,v) \in G} dp[v][dis[u] + j + w(u, v) - dis[v]]cppdp[u][j]={10ifelseu=t+_(u,v)Gdp[v][dis[u]+j+w(u,v)dis[v]]

不过对于j > kj>k,置dp[u][j] = 0dp[u][j]=0。

注意到走到终点后可以再绕圈子,所以终点不是直接置0。

最后的ans取dp[s][0]dp[s][0]即可。

接下来讨论该动态规划的复杂度,状态数为nknk个,但对于相同的jj,工作量总和其实是边数之和,则有总工作量O(mk)O(mk)

时间复杂度\Theta(m \log m) + O(mk) = O(m(k + \log m))Θ(mlogm)+O(mk)=O(m(k+logm))

预计得分85\%85%。

算法3

0权并不可怕,因为三角不等式的性质并没有被完全破坏。

我们发现,如果出现无穷条路径,是因为存在一个符合要求的路径上相邻了一个0权环。这时,就会在dfs过程中发生循环调用。考虑同时用visvis数组做拓扑检测,如果拓扑排序失败,则说明有无穷条符合要求的路径。特别的,我们需要对无法到达终点的状态进行剪枝,因为此时发现的0环无意义。即我们在反向图跑一遍tt的最短路处理出dis2[]dis2[]。做剪枝

dis[u]+dis2[u]+j - dis[t] > k \Rightarrow dp[u][j] = 0dis[u]+dis2[u]+jdis[t]>kdp[u][j]=0

时间复杂度同算法2。

预计得分100\%100%。

样例代码

#include <climits>
#include <cstdio>
#include <cstring>
#include <cstdlib>

#include <algorithm>
#include <functional>
#include <queue>

#define LOG(FMT...) // fprintf(stderr, FMT)

using namespace std;

struct node {
    int u, step;

    node() {}

    node(int u, int step) : u(u), step(step) {}

    bool operator>(const node& x) const
    { return step > x.step; }
};

struct edge {
    int v, w;
    edge* next;
};

const int N = 100010;

int n, m, k, p, sp;
edge *pp;
int dis1[N], dis2[N];
edge *g1[N], *g2[N];
edge pool[N << 2];
int dp[N][51];
bool vis[N][51];

void add_edge(edge** gp, int u, int v, int w);
void dij(int* disp, edge** gp, int u);
bool dfs(int u, int ck);

int main() {
    int t, u, v, w;
    scanf("%d", &t);
    while (t--) {
        pp = pool;
        memset(dis1, -1, sizeof(dis1));
        memset(dis2, -1, sizeof(dis2));
        memset(g1, 0, sizeof(g1));
        memset(g2, 0, sizeof(g2));
        memset(dp, -1, sizeof(dp));
        memset(vis, 0, sizeof(vis));

        scanf("%d%d%d%d", &n, &m, &k, &p);
        while (m--) {
            scanf("%d%d%d", &u, &v, &w);
            add_edge(g1, u, v, w);
            add_edge(g2, v, u, w);
        }

        dij(dis1, g1, 1);
        dij(dis2, g2, n);

        sp = dis2[1];
        if (dfs(1, 0))
            puts("-1");
        else
            printf("%d\n", dp[1][0]);
    }
    return 0;
}

inline void add_edge(edge** gp, int u, int v, int w) {
    pp->v = v;
    pp->w = w;
    pp->next = gp[u];
    gp[u] = pp;
    ++pp;
}

inline void dij(int* disp, edge** gp, int u) {
    priority_queue<node, vector<node>, greater<node> > q;
    node t;
    disp[u] = 0;
    q.push(node(u, 0));
    while (!q.empty()) {
        t = q.top();
        q.pop();
        u = t.u;
        if (disp[u] < t.step)
            continue;
        for (edge* p = gp[u]; p; p = p->next)
            if (disp[p->v] == -1 || disp[p->v] > disp[u] + p->w) {
                disp[p->v] = disp[u] + p->w;
                q.push(node(p->v, disp[p->v]));
            }
    }
}

bool dfs(int u, int ck) {
    if (vis[u][ck])
        return true;
    if (~dp[u][ck])
        return false;
    vis[u][ck] = true;

    if (u == n)
        dp[u][ck] = 1;
    else
        dp[u][ck] = 0;
    int cp;
    for (edge* p = g1[u]; p; p = p->next) {
        cp = dis1[u] + p->w + ck - dis1[p->v];
        if (cp <= k && dis1[u] + ck + p->w + dis2[p->v] - sp <= k) {
            if (dfs(p->v, cp))
                return true;
            dp[u][ck] += dp[p->v][cp];
            if (dp[u][ck] >= ::p)
                dp[u][ck] -= ::p;
        }
    }

    vis[u][ck] = false;
    return false;
}

D2T1 奶酪

有一个3维空间下的无限广阔的奶酪。可以表示成在0 \le z \le h0zh的区域都有奶酪。但是存在例外即有一些球形空腔可表示为\sqrt{(x - x_i)^2 + (y - y_i) ^ 2 + (z - z_i)^2} \le r(xxi)2+(yyi)2+(zzi)2r。小老鼠在奶酪的下表面,他可以通过一些和下表面相切或相交的空洞到达空腔中,然后可以在相切或相交的空腔中随意走动,他想知道他能不能到达奶酪的上表面。

有多组输入t \le 20t20,奶酪洞数量n \le 10^3n103,其他数值x, y, z, r, h \le 10^9x,y,z,r,h109

算法1

枚举两个球能不能走到,并查集合并。

其实不用精度损失,直接将双方平方,能通过当且仅当

(x\_i - x\_j)^2 + (y\_i - y\_j) ^ 2 + (z\_i - z\_j)^2 \le (2r)^2(x_ix_j)2+(y_iy_j)2+(z_iz_j)2(2r)2

单个输入有时间复杂度\Theta(n^2) + \Theta(n \alpha(n)) = \Theta(n^2)Θ(n2)+Θ(nα(n))=Θ(n2)。

预计得分100\%100%

样例代码

#include <climits>
#include <cstdio>
#include <cstring>
#include <cstdlib>

#include <algorithm>
#include <functional>

#define LOG(FMT...) fprintf(stderr, FMT)

using namespace std;

typedef long long ll;

const int N = 1010;

int n, S, T;
ll h, r, d2;
int f[N], rk[N];
ll x[N], y[N], z[N];

ll square(ll x);
int find(int x);
void merge(int x, int y);

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        scanf("%d%lld%lld", &n, &h, &r);
        for (int i = 1; i <= n; ++i)
            scanf("%lld%lld%lld", &x[i], &y[i], &z[i]);

        d2 = square(r * 2);
        S = 0;
        T = n + 1;
        for (int i = n + 1; ~i; --i)
            f[i] = i;
        memset(rk, 0, sizeof(rk));
        for (int i = 1; i <= n; ++i) {
            if (z[i] <= r && z[i] >= -r)
                merge(S, i);
            if (z[i] >= h - r && z[i] <= h + r)
                merge(T, i);
        }
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j < i; ++j)
                if (square(x[i] - x[j]) + square(y[i] - y[j]) + square(z[i] - z[j]) <= d2)
                    merge(i, j);
        puts(find(S) == find(T) ? "Yes" : "No");
    }
    return 0;
}

inline ll square(ll x)
{ return x * x; }

int find(int x) {
    if (f[x] == x)
        return x;
    return f[x] = find(f[x]);
}

inline void merge(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y)
        return;
    if (rk[x] > rk[y]) {
        f[y] = x;
        return;
    }
    if (rk[x] < rk[y]) {
        f[x] = y;
        return;
    }
    f[y] = x;
    ++rk[x];
}

D2T2 宝藏

算法1

对于部分的数据点是一颗树。只用枚举从哪个点开始开路即可。

时间复杂度\Theta(n^2)Θ(n2),预计得分30\%30%。

算法2

考虑进行dp。

dp[S][u][j]dp[S][u][j]表示现在要规划的点集合是SS,其起点在ii,起点的里出发点已经经过了jj个节点。

易列方程

dp[S][u][j] = \min\_{A \subset S} \min\_{v \in A, (u, v) \in G} ( dp[A][v][j + 1] + dp[\complement\_S A][u][j] + w(u, v) \times j )dp[S][u][j]=min_ASmin_vA,(u,v)G(dp[A][v][j+1]+dp[_SA][u][j]+w(u,v)×j)

有边界条件dp[\{u\}][u][j] = 0dp[{u}][u][j]=0。

时间复杂度\Theta(3^n n^3)Θ(3nn3)。预计得分80\%80%。

算法3

会有许多点到起点经过的点数是相同的。考虑优化这些部分?

dp[S][j]dp[S][j]表示当前SS内的点如果向外联,则代价是w \times jw×j。此时规划出的最优方案。

dp[S][j] = \min\_{A \subset S} (dp[A][j + 1] + j \times cost[\complement\_S A][\complement\_V S])dp[S][j]=min_AS(dp[A][j+1]+j×cost[_SA][_VS])

这里要预处理cost[A][S]cost[A][S]表示AA中的每一点uu到SS的某一个点中距离的最小值

时间复杂度\Theta(3^n n) + \Theta(3^n n) = \Theta(3^n n)Θ(3nn)+Θ(3nn)=Θ(3nn),预计得分100\%100%。

样例代码

我这里写的cost是点与集合的,复杂度是\Theta(3^n n^2)Θ(3nn2)。其实按数据范围也是可过的。

#include <cstdio>
#include <cstring>

#include <algorithm>

#define LOG(FMT...) // fprintf(stderr, FMT)

using namespace std;

const int N = 12, L = 1 << N;

int n, U;
int g[N][N];
int cost[L][N], dp[L][N];

int check(int x, int y);
int dfs(int s, int dep);
int calc(int s, int u);

int main() {
    memset(g, -1, sizeof(g));
    fill(cost[0], cost[L - 1] + N, -2);
    fill(dp[0], dp[L - 1] + N, -2);
    int m, u, v, w, ans = -1;
    scanf("%d%d", &n, &m);
    while (m--) {
        scanf("%d%d%d", &u, &v, &w);
        --u;
        --v;
        g[u][v] = g[v][u] = check(g[u][v], w);
    }
    U = (1 << n) - 1;
    for (u = 0; u < n; ++u)
        ans = check(ans, dfs(U ^ (1 << u), 1));
    printf("%d\n", ans);
    return 0;
}

inline int check(int x, int y) {
    if (x == -1)
        return y;
    if (y == -1)
        return x;
    return min(x, y);
}

int dfs(int s, int dep) {
    if (dp[s][dep] != -2)
        return dp[s][dep];
    if (!s)
        return dp[s][dep] = 0;
    dp[s][dep] = -1;
    int cur;
    for (int i = 0; i != s; ++i)
        if ((i | s) == s) {
            cur = dfs(i, dep + 1);
            if (cur == -1)
                continue;
            for (int u = 0; u < n; ++u)
                if (((1 << u) | (s ^ i)) == (s ^ i)) {
                    if (calc(U ^ s, u) == -1) {
                        cur = -1;
                        break;
                    }
                    cur += dep * cost[U ^ s][u];
                }
            dp[s][dep] = check(dp[s][dep], cur);
        }
    return dp[s][dep];
}

inline int calc(int s, int u) {
    if (cost[s][u] != -2)
        return cost[s][u];
    if (!s)
        return cost[0][u] = -1;
    cost[s][u] = -1;
    for (int v = 0; v < n; ++v)
        if (((1 << v) | s) == s)
            return cost[s][u] = check(g[u][v], calc(s ^ (1 << v), u));
    return cost[s][u];
}

D2T3 列队

算法1

暴力swap

时间复杂度\Theta(q(n+m))Θ(q(n+m))。预计得分30\%30%。

算法2

思考n=1n=1的情况。

这时只是维护一个队列拿走一个元素以及从末尾插入一个元素。可以用平衡树维护。

推广该思想,一个n \times mn×m的方阵看做n+1n+1个队列。每行的前m-1m1个人算一个队列,最后一行算一个队列。

时间复杂度\Theta(mn + q(\log n + \log m))Θ(mn+q(logn+logm))。空间复杂度\Theta(nm)Θ(nm)。预计得分50\%50%。

算法3

考虑将一段连续的区间抽象成一个节点。

时间复杂度\Theta(n + q(\log n + \log m))Θ(n+q(logn+logm))。空间复杂度\Theta(n + q)Θ(n+q)。预计得分100\%100%。

样例代码

#include <cstdio>
#include <ctime>

#include <algorithm>
#include <utility>

#define GET_CH(P, F) ((F) ? (P)->rs : (P)->ls)
#define LOG(FMT...) fprintf(stderr, FMT)

using namespace std;

typedef long long ll;

struct data {
    int step, sz;
    ll start;

    data() {}

    data(int step, int sz, ll start) : step(step), sz(sz), start(start) {}

    data(ll x) : step(0), sz(1), start(x) {}
};

struct node {
    int cnt, rnd;
    data x;
    node *ls, *rs;
};

struct que {
    node* t;

    void insert(ll x);
    ll remove(int x);
};

const int N = 300010;

node *res1, *res2;
int resy;
que q1[N], q2;

int fast_rand();
node* create(const data& x);
int get_cnt(node* p);
void ninsert(node*& p, node* q, int x);
void rotate(node*& p, bool f);
void update(node* p);
pair<data, int> nremove(node*& p, int x);
void debug_order(node* p);

int main() {
    int n, m, q, x, y;
    ll id1, id2;
    scanf("%d%d%d", &n, &m, &q);
    for (int i = 1; i <= n; ++i)
        q1[i].t = create(data(1, m - 1, (ll)(i - 1) * m));
    q2.t = create(data(m, n, 0));
    while (q--) {
        scanf("%d%d", &x, &y);
        if (y == m) {
            id2 = q2.remove(x);
            printf("%lld\n", id2);
            q2.insert(id2);
        } else {
            id1 = q1[x].remove(y);
            printf("%lld\n", id1);
            id2 = q2.remove(x);
            q1[x].insert(id2);
            q2.insert(id1);
        }
    }
    return 0;
}

inline int fast_rand() {
    static int x = time(NULL) << 4, y = time(NULL) >> 2, z = time(NULL);
    int t;
    x ^= x << 16;
    x ^= x >> 5;
    x ^= x << 1;
    t = x;
    x = y;
    y = z;
    z = t ^ x ^ y;
    return z;
}

inline node* create(const data& x) {
    static node pool[N << 3];
    static node* p = pool;
    ++p;
    p->rnd = fast_rand();
    p->x = x;
    p->cnt = x.sz;
    return p;
}

inline void rotate(node*& p, bool f) {
    node *son = GET_CH(p, f), *pson = GET_CH(son, !f);
    GET_CH(p, f) = pson;
    GET_CH(son, !f) = p;
    update(p);
    p = son;
    update(p);
}

inline int get_cnt(node* p)
{ return p ? p->cnt : 0; }

inline void update(node* p)
{ p->cnt = get_cnt(p->ls) + get_cnt(p->rs) + p->x.sz; }

void que::insert(ll x) 
{ ninsert(t, create(data(x)), get_cnt(t) + 1); }

ll que::remove(int x) {
    pair<data, int> p = nremove(t, x);
    data d = p.first;
    int y = p.second;
    if (res2)
        ninsert(t, res2, resy + 1);
    if (res1)
        ninsert(t, res1, resy + 1);
    ll cur = (ll)(x - y) * d.step + d.start;
    if (x - y < d.sz)
        ninsert(t, create(data(d.step, d.sz - x + y, cur)), y + 1);
    if (x - y > 1)
        ninsert(t, create(data(d.step, x - y - 1, d.start)), y + 1);
    return cur;
}

pair<data, int> nremove(node*& p, int x) {
    pair<data, int> ret;
    if (x <= get_cnt(p->ls)) {
        ret = nremove(p->ls, x);
        update(p);
        return ret;
    }
    if (get_cnt(p->ls) + p->x.sz >= x) {
        ret = make_pair(p->x, get_cnt(p->ls));
        res1 = p->ls;
        res2 = p->rs;
        p = NULL;
        resy = 0;
        return ret;
    }
    ret = nremove(p->rs, x - get_cnt(p->ls) - p->x.sz);
    resy += get_cnt(p->ls) + p->x.sz;
    ret.second += get_cnt(p->ls) + p->x.sz;
    update(p);
    return ret;
}

void ninsert(node*& p, node* q, int x) {
    if (!p) {
        p = q;
        return;
    }
    if (x <= get_cnt(p->ls) + p->x.sz) {
        ninsert(p->ls, q, x);
        update(p);
        if (p->rnd < p->ls->rnd)
            rotate(p, 0);
        return;
    }
    ninsert(p->rs, q, x - get_cnt(p->ls) - p->x.sz);
    update(p);
    if (p->rnd < p->rs->rnd)
        rotate(p, 1);
    return;
}

void debug_order(node* p) {
    if (!p)
        return;
    debug_order(p->ls);
    LOG("(%lld %d %d) ", p->x.start, p->x.step, p->x.sz);
    debug_order(p->rs);
}