2019 Multi-University Training Contest 3

好吧,还是决定写题解了,今天开局搞05,然后自闭一小时,此时我们队还是爆零状态,赶紧把06切了,然后放手搞02,和09,双重自闭…然后赶紧去秒了07,搞着搞着突然想玩一波玄学,09费用流建图只加部分的边,然后就过了…接下来搞04,想明白后就是个二分+线段树优化dp,cf上老套路了,一发切了,最后终于是取得了湖南省内第一,庆祝一下下
1002 Blow up the city

解法:我们先倒着建图,加一个虚点,虚点连接所有入度为0(倒着的)的点,然后建立支配树,每次查询 u v,答案就是dep[u] + dep[v] - dep[lca] - 1,为啥要减1,是因为虚点也算进去了。
#include
#define ll long long
#define pb push_back
using namespace std;
const int maxn = 1e5 + 10;
vector<int> pre[maxn], dom[maxn], G[maxn];
int n, m, semi[maxn], idom[maxn], tot;
int id[maxn], dfn[maxn], fa[maxn], father[maxn], val[maxn];
void dfs(int x) {
    dfn[x] = ++tot;
    id[tot] = x;
    for (auto v : G[x]) {
        pre[v].pb(x);
        if (!dfn[v]) {
            fa[v] = x;
            dfs(v);
        }
    }
}
int get(int x) {
    if (father[x] == x)
        return x;
    int y = get(father[x]);
    if (dfn[semi[val[father[x]]]] < dfn[semi[val[x]]])
        val[x] = val[father[x]];
    return father[x] = y;
}
int smin(int x,int y) {return dfn[x] < dfn[y] ? x : y; }
void solve() {
    for (int i = tot; i >= 2; i--) {
        int u = id[i];
        for (auto v : pre[u])
            if (dfn[v] < dfn[u])
                semi[u] = smin(semi[u], v);
            else {
                get(v);
                semi[u] = smin(semi[u], semi[val[v]]);
            }
        father[u] = fa[u];
        dom[semi[u]].pb(u);
        for (auto v : dom[fa[u]]) {
            get(v);
            int x = val[v];
            idom[v] = (dfn[semi[x]] < dfn[semi[v]]) ? x : fa[u];
        }
    }
    for (int i = 2; i <= tot; i++) {
        int x = id[i];
        if (idom[x] != semi[x])
            idom[x] = idom[idom[x]];
    }
}
int vis[maxn], f[maxn][20], dep[maxn];
void build() {
    for (int i = 1; i <= n; i++)
        G[i].clear();
    for (int i = 1; i <= n; i++)
        if (idom[i])
            G[idom[i]].pb(i);
}
void dfs2(int u, int Fa) {
    dep[u] = dep[Fa] + 1;
    f[u][0] = Fa;
    for (int i = 1; i < 20; i++)
        f[u][i] = f[f[u][i - 1]][i - 1];
    for (auto v : G[u])
        dfs2(v, u);
}
int LCA(int x, int y) {
    if (dep[x] < dep[y])
        swap(x, y);
    for (int i = 19; i >= 0; i--)
        if (dep[f[x][i]] >= dep[y])
            x = f[x][i];
    if (x == y)
        return x;
    for (int i = 19; i >= 0; i--)
        if (f[x][i] != f[y][i])
            x = f[x][i], y = f[y][i];
    return f[x][0];
}
int main() {
    int u, v, T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);
        tot = 0;
        for (int i = 1; i <= n + 1; i++) {
            pre[i].clear(), dom[i].clear(), G[i].clear();
            dfn[i] = idom[i] = vis[i] = 0;
            father[i] = val[i] = semi[i] = i;
        }
        for (int i = 1; i <= m; i++) {
            scanf("%d%d", &u, &v);
            G[v].pb(u);
            vis[u] = 1;
        }
        n++;
        for (int i = 1; i < n; i++)
            if (!vis[i])
                G[n].pb(i);
        dfs(n);
        solve();
        build();
        dfs2(n, 0);
        int q;
        scanf("%d", &q);
        while (q--) {
            scanf("%d%d", &u, &v);
            int lca = LCA(u, v);
            printf("%d\n", dep[u] + dep[v] - dep[lca] - 1);
        }
    }
}

1004 Distribution of books

题意:要求把一个序列分成连续的k块(可以去掉后缀),使得权值和最大的那一块权值最小,求出这个最小值
解法:二分这个权值,然后根据权值去把序列切块,如果能切成k块甚至更多,那么一定合法,否则不合法,怎么check呢?设d[i]为前 i 个数在最大权值限制在T时最多能切的块数,转移:对于所有的 L < i,如果sum[i] - sum[L] <= T,那么d[i] = max(d[i], d[L] + 1),很显然这个dp check复杂度是n^2,我们变形:sum[i] - T <= sum[L],我们二分找到最小的sum[L],然后在线段树中查其区间[sum[L], max(sum[x])]最大值mx,d[i] = mx + 1即可,然后在线段树的 sum[i]点更新d[i]。
#include
#define ll long long
using namespace std;
const int maxn = 2e5 + 10;
int mx[maxn * 4], a[maxn], sz, n, p[maxn], inf = 1e9;
ll sum[maxn], b[maxn];
#define ls o * 2
#define rs o * 2 + 1
#define m (l + r) / 2
int qu(int o, int l, int r, int ql, int qr) {
    if (l >= ql && r <= qr)
        return mx[o];
    int res = 0;
    if (ql <= m)
        res = max(res, qu(ls, l, m, ql, qr));
    if (qr > m)
        res = max(res, qu(rs, m + 1, r, ql, qr));
    return res;
}
void up(int o, int l, int r, int k, int v) {
    if (l == r) {
        mx[o] = v;
        return;
    }
    if (k <= m)
        up(ls, l, m, k, v);
    else
        up(rs, m + 1, r, k, v);
    mx[o] = max(mx[ls], mx[rs]);
}
int ok(ll T, int k) {
    for (int i = 1; i <= sz * 4; i++)
        mx[i] = 0;
    for (int i = 1; i <= n; i++) {
        int cur = lower_bound(b + 1, b + 1 + sz, sum[i] - T) - b;
        int v = qu(1, 1, sz, cur, sz);
        if (v == k - 1) {
            if (v || (!v && sum[i] <= T))
                return 1;
        }
        if (v || (!v && sum[i] <= T))
            up(1, 1, sz, p[i], v + 1);
    }
    return 0;
}
int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        int mn = 1e9, k;
        scanf("%d%d", &n, &k);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            mn = min(mn, a[i]);
            sum[i] = sum[i - 1] + a[i];
            b[i] = sum[i];
        }
        sort(b + 1, b + 1 + n);
        sz = unique(b + 1, b + 1 + n) - b - 1;
        for (int i = 1; i <= n; i++)
            p[i] = lower_bound(b + 1, b + 1 + sz, sum[i])  -b;
        ll N = abs(1ll * mn * n);
        ll L = 0, R = N + 1e9;
        while (L < R) {
            ll mid = (L + R) / 2;
            if (!ok(mid - N, k))
                L = mid + 1;
            else
                R = mid;
        }
        printf("%lld\n", L - N);
    }
}

1005 Easy Math Problem
待补
1006 Fansblog
快速幂套快速乘。签到

#include
#define ll long long
using namespace std;
const int maxn = 1e7 + 10000;
int vis[maxn], pri[maxn / 10], cnt;
ll mod;
void init(int n) {
    int ans = 0;
    for (int i = 2; i <= n; i++) {
        if (!vis[i])
            pri[++cnt] = i;
        for (int j = 1; j <= cnt && pri[j] * i <= n; j++) {
            vis[pri[j] * i] = 1;
            if (i % pri[j] == 0)
                break;
        }
    }
}
int ok(ll n) {
    for (int i = 1; 1ll * pri[i] * pri[i] <= n; i++)
        if (n % pri[i] == 0)
            return 0;
    return 1;
}
void add(ll &x, ll y) {
    x += y;
    if (x >= mod)
        x -= mod;
}
ll ksc(ll &x, ll y) {
    ll res = 0;
    while (y) {
        if (y & 1)
            add(res, x);
        add(x, x);
        y /= 2;
    }
    x = res;
}
ll ksm(ll x, ll y) {
    ll res = 1;
    while (y) {
        if (y & 1)
            ksc(res, x);
        ksc(x, x);
        y /= 2;
    }
    return res;
}
int main() {
    int T;
    init(1e7 + 1000);
    cin>>T;
    while (T--) {
        ll n;
        cin>>mod;
        for (n = mod - 1; n; n--)
            if (ok(n))
                break;
        //printf("n = %lld\n", n);
        //continue;
        ll ans = 1;
        for (ll i = mod - 2; i > n; i--) {
            ll inv = ksm(i, mod - 2);
            ksc(ans, inv);
        }
        cout<<ans<<endl;
    }
}

1007 Find the answer
cf原题,线段树水题

#include
#define ll long long
using namespace std;
const int maxn = 2e5 + 10;
ll sum[maxn * 4];
int a[maxn], b[maxn], val[maxn * 4];
#define ls o * 2
#define rs o * 2 + 1
#define mid (l + r) / 2
void up(int o, int l, int r, int k) {
    if (l == r) {
        sum[o] += b[l];
        val[o]++;
        return;
    }
    if (k <= mid)
        up(ls, l, mid, k);
    else
        up(rs, mid + 1, r, k);
    sum[o] = sum[ls] + sum[rs];
    val[o] = val[ls] + val[rs];
}
int qu(int o, int l, int r, ll v) {
    if (l == r) {
        if (!v || !b[l])
            return 0;
        return (v + (b[l] - 1)) / b[l];
    }
    if (sum[rs] < v)
        return qu(ls, l, mid, v - sum[rs]) + val[rs];
    return qu(rs, mid + 1, r, v);
}
int main()
{
    int Cas;
    scanf("%d", &Cas);
    while (Cas--) {
        int n, m;
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++) {
            scanf("%d" ,&a[i]);
            b[i] = a[i];
        }
        sort(b + 1, b + 1 + n);
        int sz = unique(b + 1, b + 1 + n) - b - 1;
        for (int i = 1; i <= sz * 4; i++)
            sum[i] = val[i] = 0;
        ll res = 0;
        for (int i = 1; i <= n; i++) {
            res += a[i];
            int ans = 0;
            if (res > m)
                ans = qu(1, 1, sz, res - m);
            int k = lower_bound(b + 1, b + 1 + sz, a[i]) - b;
            up(1, 1, sz, k);
            printf("%d ", ans);
        }
        puts("");
    }
}

1008 Game

题意:有两种操作: 1 l r,查询这个区间有多少子区间异或和为0,2 pos,将a[pos]的值与a[pos + 1]互换
解法:很明显的带修改莫队,我们维护异或前缀和,每次移动端点看该端点前缀异或和的值出现了几次即可。
#include
#define ll long long
using namespace std;
const int maxn = 1e5 + 10, N = 1024 * 1025;
int cnt[N], a[maxn], b[maxn], block;
ll ans[maxn];
struct node {
    int id, l, r, t;
    bool operator<(const node &tmp) const {
        if (l / block != tmp.l / block)
            return l < tmp.l;
        else if (r / block != tmp.r / block)
            return r < tmp.r;
        return t < tmp.t;
    }
} q[maxn];
struct node2 {
    int pos, t, pre, val;
} d[maxn];
int gao(int x, int opt) {
    cnt[x] += opt;
    if (opt == 1)
        return cnt[x] - 1;
    return cnt[x];
}
int main() {
    int n, m, opt, l, r;
    while (~scanf("%d%d", &n, &m)) {
        for (int i = 0; i <= 1024 * 1024; i++)
            cnt[i] = 0;
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]), b[i] = b[i - 1] ^ a[i];
        int time = 0, ID = 0;
        for (int i = 1; i <= m; i++) {
            scanf("%d", &opt);
            if (opt == 1) {
                scanf("%d%d", &l, &r);
                ++ID; --l;
                q[ID] = node{ID, l, r, time};
            }
            else {
                scanf("%d", &l);
                int pre = b[l];
                int val = b[l] ^ a[l] ^ a[l + 1];
                ++time;
                d[time] = node2{l, time, pre, val};
                swap(a[l], a[l + 1]);
                b[l] = val;
            }
        }
        block = pow(n, 2.0 / 3) + 1;
        sort(q + 1, q + 1 + ID);
        l = 1, r = 0;
        ll sum = 0;
        for (int i = 1; i <= ID; i++) {
            while (l < q[i].l)
                sum -= gao(b[l++], -1);
            while (l > q[i].l)
                sum += gao(b[--l], 1);
            while (r < q[i].r)
                sum += gao(b[++r], 1);
            while (r > q[i].r)
                sum -= gao(b[r--], -1);
            while (time > q[i].t) {
                if (d[time].pos >= l && d[time].pos <= r) {
                    sum -= gao(d[time].val, -1);
                    sum += gao(d[time].pre, 1);
                }
                b[d[time].pos] = d[time].pre;
                time--;
            }
            while (time < q[i].t) {
                ++time;
                if (d[time].pos >= l && d[time].pos <= r) {
                    sum -= gao(d[time].pre, -1);
                    sum += gao(d[time].val, 1);
                }
                b[d[time].pos] = d[time].val;
            }
            ans[q[i].id] = 1ll * (r - l + 1) * (r - l) / 2 - sum;
        }
        for (int i = 1; i <= ID; i++)
            printf("%lld\n", ans[i]);
    }
}

1009 K Subsequence

题意:对于一个长度为n的序列,选取k个子序列(每个点只能被选一次),求k个子序列的总和最大。
解法:玄学过的这题,对于每个点拆为入点和出点,连接入点到出点费用为负的点权,流量为1,源点SS连接所有点入点,所有点出点连接汇点T,流量为1,费用为0,对于每个点ai,往后面找最多100个权值大于等于ai的 j 点(别问为啥100个,玄学),i 的出点连接 j 的入点,最后超级源S连接SS,流量为k,费用为0,然后跑一遍费用流即可
#include
using namespace std;
const int maxn=4020,inf=1e9;
struct Edge
{
    int from,to,cap,flow,cost;
    Edge(int a,int b,int c,int d,int e)
    {
        from=a,to=b,cap=c,flow=d,cost=e;
    }
};
struct MCMF{
    int n,m,s,t;
    vector<Edge>edges;
    vector<int>G[maxn];
    int inq[maxn];
    int d[maxn];
    int p[maxn];
    int a[maxn];
    void init(int n)
    {
        this->n=n;
        for(int i=0;i<=n;i++)G[i].clear();
        edges.clear();
    }
    void add(int from,int to,int cap,int cost)
    {
        edges.push_back(Edge(from,to,cap,0,cost));
        edges.push_back(Edge(to,from,0,0,-cost));
        m=edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }
    bool bellman(int s,int t,int& flow,int& cost)
    {
        for(int i=0;i<=n;i++)d[i]=inf,inq[i]=0;
        d[s]=0;inq[s]=1;p[s]=0;a[s]=inf;
        queue<int>Q;
        Q.push(s);
        while(!Q.empty())
        {
            int u=Q.front();Q.pop();
            inq[u]=0;
            for(int i=0;i<G[u].size();i++)
            {
                Edge& e=edges[G[u][i]];
                if(e.cap>e.flow&&d[e.to]>d[u]+e.cost)
                {
                    d[e.to]=d[u]+e.cost;
                    p[e.to]=G[u][i];
                    a[e.to]=min(a[u],e.cap-e.flow);
                    if(!inq[e.to])
                    {
                        Q.push(e.to);
                        inq[e.to]=1;
                    }
                }
            }
        }
        if(d[t]==inf)return false;
        flow+=a[t];
        cost+=d[t]*a[t];
        int u=t;
        while(u!=s)
        {
            edges[p[u]].flow+=a[t];
            edges[p[u]^1].flow-=a[t];
            u=edges[p[u]].from;
        }
        return true;
    }
    int mincost(int s,int t)
    {
        int flow=0,cost=0;
        while(bellman(s,t,flow,cost));
        return cost;
    }
}solve;
int a[maxn], q[maxn];
int main()
{
    int Cas;
    scanf("%d", &Cas);
    while (Cas--) {
        int n, k;
        scanf("%d%d", &n, &k);
        int S = 0, T = n * 2 + 1, SS = T + 1;
        solve.init(SS + 1);
        solve.add(S, SS, k, 0);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            solve.add(i, i + n, 1, -a[i]);
            solve.add(i + n, T, 1, 0);
            solve.add(SS, i, 1, 0);
        }
        for (int i = 1; i < n; i++)
            for (int j = i + 1, ok = 0; j <= n && ok <= 100; j++)
                if (a[j] >= a[i])
                    ok++, solve.add(i + n, j, 1, 0);
        printf("%d\n", -solve.mincost(S, T));
    }
}

dij版费用流,可以建满图:

#include 
using namespace std;
#define PII pair 
#define fr first
#define sc second
#define mp make_pair
#define INF 0x3f3f3f3f
const int N = 4010;
int n,k;
int a[N];

struct MinCostFlow{
    struct edge{
        int v,f,c,r;
        edge(int _v, int _f, int _c, int _r) :v(_v), f(_f), c(_c), r(_r) {}
    };
    int V=0,h[N],dis[N],pv[N],pe[N];
    vector <edge> G[N];

     void init(int n){
        for(int i=0;i<=V;i++) G[i].clear();
        V=n;
    }

     void add(int u,int v,int f,int c){
        G[u].push_back(edge(v,f,c,G[v].size() ));
        G[v].push_back(edge(u,0,-c,G[u].size()-1 ));
    }

    PII MCMF(int s,int t,int Flow){
        int cost=0,flow=0,newflow;fill(h,h+1+V,0);
        while(Flow){
            priority_queue <PII,vector<PII>,greater<PII> > Q;
            fill(dis,dis+V+1,INF);
            dis[s]=0,Q.push(mp(0,s));
            while(!Q.empty()){
                PII now=Q.top();Q.pop();
                int u=now.sc;
                if(dis[u]<now.fr) continue;
                for(int i=0;i<G[u].size();i++){
                    edge &e=G[u][i];
                    if(e.f>0&&dis[e.v]>dis[u]+e.c+h[u]-h[e.v]){
                        dis[e.v]=dis[u]+e.c+h[u]-h[e.v];
                        pv[e.v]=u,pe[e.v]=i;
                        Q.push(PII(dis[e.v],e.v));
                    }
                }
            }
            if(dis[t]==INF) break;
            for(int i=0;i<=V;i++) h[i]+=dis[i];
            newflow=Flow;
            for(int x=t;x!=s;x=pv[x]) newflow=min(newflow,G[pv[x]][pe[x]].f);
            Flow-=newflow,flow+=newflow,cost+=newflow*h[t];
            for(int x=t;x!=s;x=pv[x]){
                edge &e=G[pv[x]][pe[x]];
                e.f-=newflow;
                G[x][e.r].f+=newflow;
            }
        }
        return mp(flow,cost);
    }
}A;

int cas;

int main(){
    int T;
    scanf("%d", &T);
    while(T--) {
        scanf("%d%d", &n, &k);
        int S = 0, T = 2 * n + 1, SS = T + 1;
        A.init(SS+1);
        A.add(S, SS, k, 0);
        for(int i=1;i<=n;i++){
            scanf("%d", &a[i]);
            A.add(i, n+i, 1, -a[i]);
            A.add(i + n, T, 1, 0);
            A.add(SS, i, 1, 0);
        }
        for(int i = 1; i < n; i++)
            for(int j = i + 1; j <= n; j++)
                if(a[j] >= a[i])
                    A.add(i + n, j, 1, 0);
        PII Ans = A.MCMF(S, T, INF);
        printf("%d\n", -Ans.sc);
    }
    return 0;
}

1011 Squrirrel

最好的官方解法:可以先考虑不删边版本,题意化简为求每个点到最远叶子距离,一次dfs找到每个点向 其子树方向走到叶子的最远距离 和次远距离 ,第二次dfs更新向父亲方向走的最远路径 ,对 于每个点的答案就是 。在考虑允许一次删边,就在DP多开一维表删边与否,并记录子 树方向到叶子的最大,次大,第三大距离和向父亲方向走的最远距离。需要注意的是更新 的时候需 要判断该节点是否是其父亲节点往叶子方向走最远,次远路径上的点,分情况转移即可
#include
#define pi pair
#define mk make_pair
using namespace std;
const int maxn = 2e5 + 10;
vector<pi> G[maxn];
int fi[maxn][2], se[maxn][2], th[maxn][2], fa[maxn][2];
int f[maxn], s[maxn], t[maxn], dis[maxn], ans[maxn];
void dfs(int u, int Fa) {
    for (auto tmp : G[u]) {
        if (tmp.first == Fa)
            continue;
        int v = tmp.first;
        int w = tmp.second;
        dis[v] = w;
        dfs(v, u);
        if (fi[u][0] < fi[v][0] + w) {
            th[u][0] = se[u][0];
            s[u] = f[u];
            se[u][0] = fi[u][0];
            f[u] = v;
            fi[u][0] = fi[v][0] + w;
        }
        else if (se[u][0] < fi[v][0] + w) {
            th[u][0] = se[u][0];
            s[u] = v;
            se[u][0] = fi[v][0] + w;
        }
        else if (th[u][0] < fi[v][0] + w)
            th[u][0] = fi[v][0] + w;
    }
    fi[u][1] = min(fi[f[u]][0], dis[f[u]] + max(fi[f[u]][1], se[f[u]][0]));
    se[u][1] = min(fi[s[u]][0], dis[s[u]] + max(fi[s[u]][1], se[s[u]][0]));
}
void dfs2(int u, int Fa) {
    ans[u] = min(max(max(fi[u][1], se[u][0]), fa[u][0]), max(fi[u][0], fa[u][1]));
    for (auto tmp : G[u]) {
        if (tmp.first == Fa)
            continue;
        int v = tmp.first;
        int w = tmp.second;
        if (v == f[u]) {
            fa[v][0] = max(fa[u][0], se[u][0]) + w;
            fa[v][1] = min(max(fa[u][0], se[u][0]), min(max(max(se[u][1], th[u][0]), fa[u][0]), max(fa[u][1], se[u][0])) + w);
        }
        else if (v == s[u]) {
            fa[v][0] = max(fa[u][0], fi[u][0]) + w;
            fa[v][1] = min(max(fa[u][0], fi[u][0]), min(max(max(fi[u][1], th[u][0]), fa[u][0]), max(fa[u][1], fi[u][0])) + w);
        }
        else {
            fa[v][0] = max(fa[u][0], fi[u][0]) + w;
            fa[v][1] = min(max(fa[u][0], fi[u][0]), min(max(max(fi[u][1], se[u][0]), fa[u][0]), max(fa[u][1], fi[u][0])) + w);
        }
        dfs2(v, u);
    }
}
int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        int n, u, v, w;
        scanf("%d", &n);
        for (int i = 0; i <= n; i++) {
            G[i].clear();
            f[i] = s[i] = 0;
            for (int j = 0; j < 2; j++)
                fi[i][j] = se[i][j] = th[i][j] = fa[i][j] = 0;
        }
        for (int i = 1; i < n; i++) {
            scanf("%d%d%d", &u, &v, &w);
            G[u].push_back(mk(v, w));
            G[v].push_back(mk(u, w));
        }
        dfs(1, 0);
        dfs2(1, 0);
        int mn = 1e9, x;
        for (int i = 1; i <= n; i++)
            if (ans[i] < mn)
                mn = ans[i], x = i;
        printf("%d %d\n", x, mn);
    }
}

你可能感兴趣的:(杭电多校,动态规划,数据结构----线段树,图论----网络流,图论----支配树)