NCD2019部分题解

A

解题思路

  突破点在二分答案上,如果想到二分的话后面就好做了。
  假设我们二分答案的大小为x,判断是否可行,首先肯定需要在长度不小于2x的线段中找。考虑枚举竖线来找符合条件的横线,对于一条竖线\({x_1, y_1, c_1}(x_1 \leq y_1)\)来说,需要判断是否存在一条横线\({x_2, y_2, c_2}(x_2\leq y_2)\),满足\(x_1+x \leq c_2 \leq y_1-x\)\(x_2+x \leq c_1 \leq y_2-x\),我们的问题有两个维度,如果想同时判断的话十分麻烦,可以考虑如何消去其中一维的影响,考虑另一维。
  这里有一个经典套路,我们发现对于横坐标x,一共分为三类,一类是横线的左端点,一类是横线的右端点,还有一类是竖线的横坐标。我们对于所有横坐标以及种类进行双关键字排序,从小到大遍历这些横坐标,如果当前横坐标是左端点,我们就把它对应的横线的纵坐标加进集合里,如果是右端点,因为横坐标是递增的,那么这个点对应的横线必定和后面的竖线没有交叉,将其从集合中删去,如果是竖线的横坐标,那我们就从集合里找一个大于等于竖线较小的纵坐标的最小的点,如果这个点小于竖线的较大的那个纵坐标,就说明有解。

代码

struct INFO {
    int a, b, c, tp;
} sgx[maxn], sgy[maxn];
int n, m; 
int check(int x) {
    int t1 = 0, t2 = 0;
    vector<INFO> sg;
    for (int i = 1; i<=n; ++i) {
        if (sgx[i].b-sgx[i].a<2*x) continue;
        sg.push_back({sgx[i].a+x, 0, sgx[i].c, 1});
        sg.push_back({sgx[i].b-x, 0, sgx[i].c, 3});
    }
    for (int i = 1; i<=m; ++i) {
        if (sgy[i].b-sgy[i].a<2*x) continue;
        sg.push_back({sgy[i].c, sgy[i].a+x, sgy[i].b-x, 2});
    }
    multiset<int> st;
    sort(sg.begin(), sg.end(), [](INFO A, INFO B) {return A.a==B.a ? A.tp<B.tp:A.a<B.a;});
    for (auto v : sg) {
        if (v.tp==1) st.insert(v.c);
        else if (v.tp==3) st.erase(st.find(v.c));
        else {
            auto it = st.lower_bound(v.b);
            if (it!=st.end() && *it<=v.c) return 1;
        }
    }
    return 0;
}
int main() { 
    IOS;
    int __; cin >> __;
    while(__--) {
        cin >> n >> m;
        for (int i = 1; i<=n; ++i) {
            cin >> sgx[i].a >> sgx[i].b >> sgx[i].c;
            if (sgx[i].a>sgx[i].b) swap(sgx[i].a, sgx[i].b);
        }
        for (int i = 1; i<=m; ++i) {
            cin >> sgy[i].a >> sgy[i].b >> sgy[i].c;
            if (sgy[i].a>sgy[i].b) swap(sgy[i].a, sgy[i].b);
        }
        int l = 0, r = 1e5;
        while(l<r) {
            int mid = (l+r+1)>>1;
            if (check(mid)) l = mid;
            else r = mid-1;
        }
        cout << l << endl;
    }
    return 0;   
} 

B

解题思路

  不难看出,能抓住Hasan的边是割边,而别的边没有影响。我们先对图中的所有连通块中的边双连通分量进行缩点,缩点后的图就变成了一片森林,我们在森林中的两棵树中连边,会变成一棵树,而割边数量会加1,如果我们对其中的一棵树上的两点连一条边,会发现两点简单路径上的所有点都会变成一个新的边双连通分量,减少的割边数量就是他们的最短距离,所以我们需要找到每棵树上任意两点的最长的最短距离,即树的直径,然后用割边数量-树的直径即为答案。

代码

const int maxn = 1e6+10;                                                               
const int maxm = 1e6+10;
struct E {
    int to, nxt;
} e[maxm];
int h[maxn], tot = 1;
void add(int u, int v) {
    e[++tot] = {v, h[u]};
    h[u] = tot;
}
int n, m;
int dfn[maxn], low[maxn], __dfn;
int sk[maxn], tp, dcc, id[maxn];
int isbrige[maxn];
vector<int> g[maxn];
void tarjan(int u, int fa) {
    dfn[u] = low[u] = ++__dfn;
    sk[++tp] = u;
    for (int i = h[u]; i; i = e[i].nxt) {
        if ((fa^1)==i) continue;
        int v = e[i].to;
        if (!dfn[v]) {
            tarjan(v, i);
            low[u] = min(low[u], low[v]);
            if (low[v]>dfn[u]) isbrige[i] = isbrige[i^1] = 1;
        }
        else low[u] = min(low[u], dfn[v]);
    }
    if (low[u]==dfn[u]) {
        int v; ++dcc;
        do {
            v = sk[tp--];
            id[v] = dcc;
        } while(v!=u);
    }
}
int maxx, vis[maxn];
int dfs(int u) {
    vis[u] = 1;
    int f = 0;
    for (auto v : g[u]) {
        if (vis[v]) continue;
        int dis = dfs(v)+1;
        maxx = max(maxx, f+dis);
        f = max(f, dis);
    }
    return f;
}
void init() {
    __dfn = tp = dcc = 0;
    clr(isbrige, 0);
    tot = 1;
    for (int i = 0; i<=n; ++i) {
        h[i] = vis[i] = dfn[i] = low[i] = sk[i] = id[i] = 0;
        g[i].clear();
    }
}
int main() { 
    IOS;
    int __; cin >> __;
    while(__--) {
        cin >> n >> m;
        init();
        for (int i = 1, a, b; i<=m; ++i) {
            cin >> a >> b;
            add(a, b); add(b, a);
        }
        for (int i = 1; i<=n; ++i) 
            if (!dfn[i]) tarjan(i, -1);
        int ans = 0;
        for (int i = 1; i<=n; ++i)
            for (int j = h[i]; j; j = e[j].nxt) {
                int v = e[j].to;
                if (isbrige[j]) g[id[i]].push_back(id[v]), ++ans;
            }
        ans /= 2;
        maxx = 0;
        for (int i = 1; i<=dcc; ++i)
            if (!vis[i]) dfs(i);
        ans -= maxx;
        cout << ans << endl;
    }
    return 0;   
} 

C

解题思路

  这题也是经典套路了,考虑\(O(n^2)\)的做法,设dp[j]为以j结尾的lis长度,我们在遍历前面比当前a[i]小的数a[j]的时候,如果dp[j]可以更新答案,那么方案数同dp[j]的方案数,如果dp[j]+1和dp[i]相同,那么方案数就可以累加。

代码

const int maxn = 2e5+10;                                                               
const int maxm = 2e6+10;
int a[maxn], dp[maxn], cnt[maxn];
int main() { 
    IOS;
    int __; cin >> __;
    while(__--) {
        int n; cin >> n;
        for (int i = 1; i<=n; ++i) cin >> a[i];
        int maxx = 0, sum = 0;
        for (int i = 1; i<=n; ++i) {
            cnt[i] = dp[i] = 1;
            for (int j = i-1; j>=1; --j) {
                if (a[j]<a[i]) {
                   if (dp[i]<dp[j]+1) {
                       dp[i] = dp[j]+1;
                       cnt[i] = cnt[j];
                   }
                   else if (dp[i]==dp[j]+1) {
                       cnt[i] = (cnt[i]+cnt[j])%MOD;
                   }
                }
            }
            if (dp[i]>maxx) maxx = dp[i], sum = cnt[i];
            else if (dp[i]==maxx) sum = (sum+cnt[i])%MOD;
        }
        cout << maxx << ' ' << sum << endl;
    }
    return 0;   
}

D略

E

解题思路

  如果没有修改操作只有询问的话,我们求正反两个字符串的哈希值就能做,如果加上修改操作,比如i的位置修改一下,对于正的字符串来说,i到n的哈希值会改变,对于颠倒后的字符串的哈希值来说,n-i+1到n的哈希值会改变。
  如果是按照常规的计算哈希值的方法的话,我们就是对区间修改一个变量,这样维护起来十分的痛苦。我的方法是把每一个哈希值都往后补0,使得每一个哈希值对应的都是P进制下的一个n位整数,这样带来的好处是,由于长度是固定的,那么修改i的值之后,i到n这个区间上改变的值也是一样的,我们区间修改的就是一个常量了。这样我们只要写一个区间修改,单点询问的线段树。修改操作就不说了,对于查询,我们用r对应的值减去l-1对应的值,再去掉末尾0,得到一段字符串的哈希值,然后用类似的方法求出颠倒后的区间的哈希值,两者比较即可。

const int maxn = 1e5+10;                                                               
const int maxm = 2e6+10;
ll lz[maxn<<2][2], tr[maxn<<2][2], f[maxn], h[maxn][2];
inline void push_down(int rt, int s) {
    if (lz[rt][s]) {
        tr[rt<<1][s] = (tr[rt<<1][s]+lz[rt][s]+MOD)%MOD;
        tr[rt<<1|1][s] = (tr[rt<<1|1][s]+lz[rt][s]+MOD)%MOD;
        lz[rt<<1][s] = (lz[rt<<1][s]+lz[rt][s]+MOD)%MOD;
        lz[rt<<1|1][s] = (lz[rt<<1|1][s]+lz[rt][s]+MOD)%MOD;
        lz[rt][s] = 0;
    }
}
void build(int rt, int l, int r, int s) {
    tr[rt][s] = lz[rt][s] = 0;
    if (l==r) {
        tr[rt][s] = h[l][s];
        return;
    }
    int mid = (l+r)>>1;
    build(rt<<1, l, mid, s);
    build(rt<<1|1, mid+1, r, s);
}
void update(int rt, int l, int r, int L, int R, int V, int s) {
    if (l>=L && r<=R) {
        tr[rt][s] = (tr[rt][s]+V+MOD)%MOD;
        lz[rt][s] = (lz[rt][s]+V+MOD)%MOD;
        return;
    }
    push_down(rt, s);
    int mid = (l+r)>>1;
    if (L<=mid) update(rt<<1, l, mid, L, R, V, s);
    if (R>mid) update(rt<<1|1, mid+1, r, L, R, V, s);
}
ll ask(int rt, int l, int r, int pos, int s) {
    if (!pos) return 0;
    if (l==r) return tr[rt][s];
    push_down(rt, s);
    int mid = (l+r)>>1;
    if (pos<=mid) return ask(rt<<1, l, mid, pos, s);
    else return ask(rt<<1|1, mid+1, r, pos, s);
}
void ck(int rt, int l, int r, int s) {
    if (l==r) {
        cout << tr[rt][s] << ' ';
        return;
    }
    push_down(rt, s);
    int mid = (l+r)>>1;
    ck(rt<<1, l, mid, s);
    ck(rt<<1|1, mid+1, r, s);
}
char str[maxn], str2[maxn];
int n, m;
void change(int pos, ll ff) {
    ll x = 1ll*(str[pos])*f[n-pos]%MOD;
    update(1, 1, n, pos, n, MOD+ff*x, 0);
    int rpos = n-pos+1;
    ll y = 1ll*(str2[rpos])*f[n-rpos]%MOD;
    //cout << x << ' ' << y << endl;
    update(1, 1, n, rpos, n, MOD+ff*y, 1);
}
ll qp(ll x, ll y) {
    x %= MOD;
    ll res = 1;
    while(y) {
        if (y&1) res = res*x%MOD;
        x = x*x%MOD;
        y >>= 1;
    }
    return res;
}
int main() { 
    IOS;
    f[0] = 1;
    for (int i = 1; i<maxn; ++i) f[i] = f[i-1]*P%MOD;
    int __; cin >> __;
    while(__--) {
        cin >> n >> m;
        cin >> str+1;
        for (int i = 1; i<=n; ++i) str2[i] = str[n-i+1];
        for (int i = 1; i<=n; ++i) h[i][0] = (h[i-1][0]+1ll*(str[i])*f[n-i])%MOD;
        for (int i = 1; i<=n; ++i) h[i][1] = (h[i-1][1]+1ll*(str2[i])*f[n-i])%MOD;
        build(1, 1, n, 0);
        build(1, 1, n, 1);
        int op, l, r, pos; char ch[10];
        while(m--) {
            cin >> op;
            if (op==1) {
                cin >> pos >> ch;
                change(pos, -1);
                str[pos] = ch[0];
                str2[n-pos+1] = ch[0];
                change(pos, 1);
            }
            else {
                cin >> l >> r;
                ll A = (ask(1, 1, n, r, 0)-ask(1, 1, n, l-1, 0)+MOD)%MOD;
                //cout << A << endl;
                A = A*qp(f[n-r], MOD-2)%MOD;
                A = (A%MOD+MOD)%MOD;
                l = n-l+1, r = n-r+1;
                if (l>r) swap(l, r);
                //cout << l << ' ' << r << endl;
                ll B = (ask(1, 1, n, r, 1)-ask(1, 1, n, l-1, 1)+MOD)%MOD;
                B = B*qp(f[n-r], MOD-2)%MOD;
                B = (B%MOD+MOD)%MOD;
                //cout << A << ' ' << B << endl;
                if (A==B) cout << "Adnan Wins" << endl;
                else cout << "ARCNCD!" << endl;
            }
            //ck(1, 1, n, 0); cout << endl;
            //ck(1, 1, n, 1); cout << endl;
            //cout << "!" << endl;
        }
    }
    return 0;   
} 

F略

G

  (代补)

H略

J

解题思路

  很简单的题,题目就两种操作,一种区间加1,一种询问区间出现最多的数,而且就一个询问,直接差分数组做就行了。

const int maxn = 1e6+10;                                                               
const int maxm = 1e6+10;
int a[maxn], b[maxn], sub[maxn];
int main() { 
    IOS;
    int __; cin >> __;
    while(__--) {
        int n, m; cin >> n >> m;
        for (int i = 0; i<=n; ++i) sub[i] = 0;
        for (int i = 1; i<=m; ++i) cin >> a[i], --a[i];
        for (int i = 1; i<=m; ++i) {
            cin >> b[i];
            if (abs(b[i])==n) {
                ++sub[a[i]];
                --sub[a[i]+1];
                ++sub[0];
                --sub[n];
            }
            else if (b[i]>=0) {
                ++sub[a[i]];
                --sub[min(n, a[i]+b[i]+1)];
                if (a[i]+b[i]>=n) {
                    b[i] = (a[i]+b[i])%n;
                    ++sub[0];
                    --sub[b[i]+1];
                }
            }
            else if (b[i]<0) {
                ++sub[max(0, a[i]+b[i])];
                --sub[a[i]+1];
                if (a[i]+b[i]<0) {
                    b[i] = (a[i]+b[i])+n;
                    ++sub[b[i]];
                    --sub[n];
                }
            }
        }
        for (int i = 1; i<=n; ++i) sub[i] += sub[i-1];
        int c = 0;
        for (int i = 0; i<n; ++i) {
            if (sub[i]>sub[c]) c = i;
            //cout << sub[i] << endl;
        }
        cout << c+1 << ' ' << sub[c] << endl;
    }   
    return 0;   
} 

K略

L略

M

  经典取log,需要注意底数为0的时候会返回-inf,而两个-inf不相等

posted @ 2022-01-05 14:00  shuitiangong  阅读(70)  评论(0编辑  收藏  举报