• 11月训练2


    ARC108

    E 题意:给定长$n$排列,初始每个数未被标记,每次操作随机选一个未被标记的且标记后满足标记数单调的数标记,如果不存在这样的数结束操作,求期望操作数

    考虑区间dp,$f_{l,r}$表示区间$[l,r]$左右端点标记时的期望操作数,那么假设$[l+1,r-1]$中可选点个数为$k$,如果$k=0$那么$f_{l,r}=0$,否则$f_{l,r}=1+sumlimits_{i=1}^k (f_{l,s_i}+f_{s_i,r})/2$,可以用树状数组优化到$O(n^2log{n})$

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 2e3+10, P = 1e9+7;
    int n, a[N], dp[N][N], inv[N];
    int L[N][N],R[N][N],f[N][N];
    
    int main() {
        scanf("%d", &n);
        inv[1] = 1;
        for (int i=2; i<=n; ++i) inv[i] = inv[P%i]*(int64_t)(-P/i)%P;
        for (int i=1; i<=n; ++i) scanf("%d", &a[i]);
        a[n+1] = n+1;
        for (int len=1; len<=n+2; ++len) {
            for (int l=0,r=len-1; r<=n+1; ++l,++r) {
                for (int t=a[r]; t<n+2; t |= t+1) ++f[l][t];
                if (len<=2||a[l]+1>a[r]-1) continue;
                int cnt = 0, w = 0;
                for (int x=a[r]-1; x>=0; x = (x&(x+1))-1) {
                    w = (w+L[l][x]+(int64_t)R[r][x])%P;
                    cnt += f[l][x];
                }
                for (int x=a[l]; x>=0; x = (x&(x+1))-1) {
                    w = (w-L[l][x]-(int64_t)R[r][x])%P;
                    cnt -= f[l][x];
                }
                if (!cnt) dp[l][r] = 0;
                else dp[l][r] = (w*(int64_t)inv[cnt]+1)%P;
                for (int t=a[r]; t<n+2; t |= t+1) L[l][t] = (L[l][t]+dp[l][r])%P;
                for (int t=a[l]; t<n+2; t |= t+1) R[r][t] = (R[r][t]+dp[l][r])%P;
            }
        }
        if (dp[0][n+1]<0) dp[0][n+1] += P;
        printf("%d
    ", dp[0][n+1]);
    }
    View Code

    F 题意:给定树,把每个点涂上$0$或$1$,假设任意两$0$点最大距离为$X$,任意两$1$点最大距离为$Y$,那么贡献为$max(X,Y)$,求$2^N$种涂色方案的贡献和

    先求出树的直径$(u,v)$,假设$u$和$v$同色,那么贡献为直径长度

    异色的话,假设贡献为$D$

    如果点$x$满足$dis(u,x)>Dwedge dis(v,x)>D$那么一定不合法

    如果点$x$满足$dis(u,x)le D wedge dis(v,x)le D$那么这个点可以涂$0$或$1$

    其余点都只能涂$1$种颜色

    如果答案为$D$,那么对于第二类点还要满足至少$1$个染成距离为$D$的颜色,这个可以容斥求一下

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 2e5+10, P = 1e9+7;
    int n, fa[N], vis[N], pw[N], dep[2][N], f[N], h[N];
    vector<int> g[N];
    int bfs(int x, int d[]) {
        for (int i=1; i<=n; ++i) vis[i] = 0;
        queue<int> q;
        fa[x] = d[x] = 0, vis[x] = 1, q.push(x);
        while (q.size()) {
            x = q.front(); q.pop();
            for (int y:g[x]) if (!vis[y]) {
                vis[y] = 1, fa[y] = x, d[y] = d[x]+1;
                q.push(y);
            }
        }
        return x;
    }
    int main() {
        scanf("%d", &n);
        for (int i=1; i<n; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            g[u].push_back(v);
            g[v].push_back(u);
        }
        int u = bfs(1,dep[0]), v = bfs(u,dep[1]);
        bfs(v,dep[0]);
        pw[0] = 1;
        int ma = 0;
        for (int i=1; i<=n; ++i) { 
            pw[i] = pw[i-1]*2%P;
            if (i!=u&&i!=v) { 
                ma = max(ma, min(dep[0][i], dep[1][i]));
                ++f[max(dep[0][i], dep[1][i])];
            }
        }
        int ans = pw[n-2]*(int64_t)dep[0][u]%P, now = 0;
        for (int D=0; D<=dep[0][u]; ++D) {
            now += f[D];
            if (D<ma) continue;
            h[D] = pw[now];
            if (D) ans = (ans+(h[D]-h[D-1])*(int64_t)D)%P;
        }
        ans = ans*2%P;
        if (ans<0) ans += P;
        printf("%d
    ", ans);
    }
    View Code

    Dwango Programming Contest 6th

    C 题意:$n$个小孩,$k$天,第$i$天随机选$a_i$个小孩发一块曲奇,假设最终第$i$个小孩拿了$c_i$块曲奇,求$c_1 imes c_2 imes ... imes c_n$的期望乘上$inom{n}{a_1} imesinom{n}{a_2} imes ... imes inom{n}{a_k}$

    期望乘总方案,那么也就是要求所有方案下的贡献和

    先写下刚看到这个题时的一些想法

    假设所有$a_i$都为$1$的话,那么很容易做,对于一个确定的序列$c$,方案数就是$frac{k!}{prodlimits_{i=1}^n c_i!}$

    所以答案也就是$k![x^k]prodlimits_{i=1}^nsumlimits_{jge 0} frac{j}{j!} x^{j}=k![x^k](e^{nx}x^n)=frac{k!n^{k-n}}{(k-n)!}$ 

    那么如果$a_i$不全是$1$,那么方案数感觉很难处理,没想出来什么好办法

    然后说下正解做法

    考虑一个转化:$k imes n$的格子,先对于每个$k$,把第$k$行选$a_k$个格子放上棋子,然后再把每列选恰好一个棋子涂黑,求方案数

    这样的话就可以简单dp了,设$f_{i,j}$表示前$i$行涂黑了$j$个棋子的方案数,枚举下一行涂黑多少列转移即可

    $dp$转移是一个卷积形式,也可以$O(knlog{n})$来做

    然后还有一个问题是,如果初始每个小孩曲奇数不为$0$的话,是否可做呢?

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e3+10, K = 22, P = 1e9+7;
    int n, k, a[N], f[K][N], C[N][N];
    void add(int &a, int64_t b) {a=(a+b)%P;}
    int main() {
        scanf("%d%d", &n, &k);
        for (int i=1; i<=k; ++i) scanf("%d", &a[i]);
        for (int i=0; i<=n; ++i) {
            C[i][0] = 1;
            for (int j=1; j<=i; ++j) C[i][j] = (C[i-1][j]+C[i-1][j-1])%P;
        }
        f[0][0] = 1;
        for (int i=1; i<=k; ++i) {
            for (int j=0; j<=n; ++j) {
                int &ret = f[i-1][j];
                if (!ret) continue;
                for (int x=0; x<=a[i]&&x+j<=n; ++x) {
                    add(f[i][j+x], C[n-j][x]*(int64_t)C[n-x][a[i]-x]%P*ret);
                }
            }
        }
        printf("%d
    ", f[k][n]);
    }
    View Code

    gym102006K

    图的最小着色问题,把每条边定向,然后求mex,由于每个点出度不超过$2$,所以答案$le 3$

    所以如果是二分图的话答案就是$2$,否则只能是$3$

    gym102439L

    题意:范围$[1,2n]$的数,每人分$2$个,随机分给$n$个人,求和的最大值唯一的概率

    打个表可以发现和的最大值不变时,方案数是不变的

    假设最大值是$i+j$,可以发现分布是$xxxxxixxjxx$的话很好计算,直接枚举最后三个数匹配,剩余数即可任意匹配

    ARC107F

    题意:$n$点$m$边无向图,每个点$i$有权值$(A_i,B_i)$,可以任选一些点删除,花费是删点的$A$值和,删点后对应边也删除,最终每个连通块得分为$B$值和的绝对值,求最大化得分和-花费

    每个点有三种情况,取$b_i$或取$-b_i$或删除,并且要求每个连通块正负要相同。

    答案先加上$sum |b_i|$,然后构造最小割即可。

    同一个连通块符号相同,可以拆点,对于边$(u,v)$连$(u,v+n,INF),(v,u+n,INF)$

    对于$b_ile 0$的连边$(S,i,0),(i,i+n,a+b),(i+n,T,2b)$,否则连$(S,i,-2b),(i,i+n,-b+a),(i+n,T,0)$

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e6+10, S = N-2, T = N-1, INF = 0x3f3f3f3f;
    int n, m, a[N], b[N];
    struct edge {
        int to,w,next;
        edge(int to=0,int w=0,int next=0):to(to),w(w),next(next) {}
    } e[N];
    int head[N], dep[N], cur[N], cnt=1;
    queue<int> Q;
    int bfs() {
        for (int i=1; i<=2*n; ++i) dep[i]=INF,cur[i]=head[i];
        dep[S]=INF,cur[S]=head[S];
        dep[T]=INF,cur[T]=head[T];
        dep[S]=0,Q.push(S);
        while (Q.size()) {
            int u = Q.front(); Q.pop();
            for (int i=head[u]; i; i=e[i].next) {
                if (dep[e[i].to]>dep[u]+1&&e[i].w) {
                    dep[e[i].to]=dep[u]+1;
                    Q.push(e[i].to);
                }
            }
        }
        return dep[T]!=INF;
    }
    int dfs(int x, int w) {
        if (x==T) return w;
        int used = 0;
        for (int i=cur[x]; i; i=e[i].next) {
            cur[x] = i;
            if (dep[e[i].to]==dep[x]+1&&e[i].w) {
                int f = dfs(e[i].to,min(w-used,e[i].w));
                if (f) used+=f,e[i].w-=f,e[i^1].w+=f;
                if (used==w) break;
            }
        }
        return used;
    }
    int dinic() {
        int ans = 0;
        while (bfs()) ans+=dfs(S,INF);
        return ans;
    }
    void add(int u, int v, int w) {
        e[++cnt] = edge(v,w,head[u]);
        head[u] = cnt;
        e[++cnt] = edge(u,0,head[v]);
        head[v] = cnt;
    }
    
    int main() {
        scanf("%d%d", &n, &m);
        int tot = 0;
        for (int i=1; i<=n; ++i) scanf("%d", &a[i]);
        for (int i=1; i<=n; ++i) scanf("%d", &b[i]), tot += abs(b[i]);
        for (int i=1; i<=m; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            add(u+n,v,INF);
            add(v+n,u,INF);
        }
        for (int i=1; i<=n; ++i) {
            if (b[i]>=0) add(i,i+n,b[i]+a[i]),add(i+n,T,2*b[i]);
            else add(S,i,-2*b[i]),add(i,i+n,-b[i]+a[i]);
        }
        printf("%d
    ", tot-dinic());
    }
    View Code

    gym101982I

    题意:给定序列,每个元素范围$[1,k]$,有些位置为$0$表示不确定的数,要求填上$[1,k]$使得逆序对最大

    首先可以发现填数一定是非降的,那么就可以得到一个dp做法

    设${dp}_{i,j}$表示前$i$个位置,最后一个位置填的数是$j$的最大值

    先除去已有的数之间的逆序对贡献,那么每次枚举填数的数和填的长度来转移

    那么有${dp}_{i,j}=maxlimits_{0le x<i,j<yle k+1} {dp}_{x,y}+calc(x+1,i,j)$

    $calc(l,r,j)$表示$[l,r]$中填$j$时对已经填好的数的贡献

    这个$dp$方程化简一下发现可以斜率优化到$O(nk)$

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 2e5+10;
    int n, k, cur, a[N], sum[N], cnt[N][105];
    int64_t dp[N][2],f[N];
    deque<int> q;
    int64_t dy(int a, int b) {
        return sum[a]*(int64_t)sum[a]+f[a]-dp[a][cur^1]-sum[b]*(int64_t)sum[b]-f[b]+dp[b][cur^1];
    }
    int64_t dx(int a, int b) {
        return sum[a]-sum[b];
    }
    int chk(int a, int b, int c) {
        return dy(a,b)*dx(b,c)<=dy(b,c)*dx(a,b);
    }
    int main() {
        scanf("%d%d", &n, &k);
        for (int i=1; i<=n; ++i) { 
            scanf("%d", &a[i]);
            memcpy(cnt[i],cnt[i-1],sizeof cnt[0]);
            sum[i] = sum[i-1]+!a[i];
            if (a[i]) {
                for (int j=a[i]; j<=k; ++j) ++cnt[i][j];
            }
        }
        int64_t ans = 0;
        for (int i=1; i<=n; ++i) if (a[i]) ans += cnt[i-1][k]-cnt[i-1][a[i]];
        if (!sum[n]) return printf("%lld
    ", ans),0;
        for (int i=1; i<=n; ++i) dp[i][0] = -1e12;
        for (int j=k; j; --j) {
            for (int i=1; i<=n; ++i) {
                f[i] = f[i-1];
                if (!a[i]) f[i] += cnt[i-1][k]-cnt[i-1][j]+cnt[n][j-1]-cnt[i][j-1];
            }
            q.clear();
            q.push_back(0);
            cur ^= 1;
            for (int i=1; i<=n; ++i) dp[i][cur] = -1e12;
            for (int i=1; i<=n; ++i) {
                if (a[i]) {
                    dp[i][cur] = dp[i-1][cur];
                    continue;
                }
                while (q.size()>1&&dy(q[1],q[0])<=dx(q[1],q[0])*sum[i]) q.pop_front();
                dp[i][cur] = dp[q[0]][cur^1]+f[i]-f[q[0]]+sum[q[0]]*(sum[i]-(int64_t)sum[q[0]]);
                dp[i][cur] = max(dp[i][cur], dp[i][cur^1]);
                while (q.size()>1&&chk(i,q.back(),q[q.size()-2])) q.pop_back();
                q.push_back(i);
            }
        }
        printf("%lld
    ", dp[n][cur]+ans);
    }
    View Code

    hitachi2020

    C. ThREE

    题意:给定树,求构造一个排列$p$,使得树上距离为$3$的点对$(i,j)$满足$p_i$与$p_j$的和或积是$3$的倍数

    首先对树二分图染色,假设两部大小为$X,Y,X<Y$,那么如果$lfloorfrac{n}{3} floorge X$,那么把所有$3$的倍数分到$X$中一定合法

    否则的话把模$3$为$1$的数的个数一定$<X$,可以全放进$X$中,不够的话再放进$3$的倍数

    ABC155E

    题意:设$f(x)$表示$x$十进制下的数位和,给定$N$,求$xge N$时$f(x)+f(x-N)$的最小值

    可以发现最优解的最高位要么等于$N$要么比$N$大$1$,相等的话可以直接递归到后面求解

    不等的话那么相当于把后面每位的数$a$变为$9-a$,个位数变为$10-a$

    所以很容易用dp求解

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e6+10;
    int n,dp[N][2];
    char a[N];
    int main() {
        scanf("%s", a+1);
        n = strlen(a+1);
        for (int i=1; i<=n; ++i) a[i] -= '0';
        reverse(a+1,a+1+n);
        dp[1][0] = a[1], dp[1][1] = 10-a[1];
        for (int i=2; i<=n+1; ++i) {
            dp[i][0] = dp[i-1][0]+a[i];
            dp[i][1] = dp[i-1][1]+9-a[i];
            if (a[i]!=9) dp[i][0] = min(dp[i][0],dp[i-1][1]+a[i]+1);
            if (a[i]) dp[i][1] = min(dp[i][1],dp[i-1][0]+9-a[i]+1);
        }
        printf("%d
    ", min(dp[n+1][0],dp[n+1][1]));
    }
    View Code

    ABC182F

    题意:$n$种硬币价格$A_i$,保证$A_{i+1}>A_{i}$并且$A_{i+1}$是$A_i$的倍数,$A_1=1$,有一个价格$X$的商品,花$Y(ge X)$元买,找回$Y-X$元,要求$Y$和$Y-X$都用最少的硬币表示时,花出的每种硬币都不会在找回,求有多少种合法的$Y$

    相当于是说给定了一个变进制数$X$,求有多少个变进制数$Y(ge 0)$,使得$Y$和$X+Y$非零位交集为空,分是否进位dp即可

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 55;
    int n;
    int64_t X, a[N], lim[N], f[N], dp[N][2];
    int main() {
        scanf("%d%lld", &n, &X);
        for (int i=1; i<=n; ++i) scanf("%lld", &a[i]);
        for (int i=1; i<n; ++i) lim[i] = a[i+1]/a[i];
        for (int i=n; i; --i) {
            f[i] = X/a[i];
            X %= a[i];
        }
        dp[0][0] = 1;
        for (int i=1; i<n; ++i) {
            dp[i][0] = dp[i-1][0];
            dp[i][1] = dp[i-1][1];
            if (f[i]) dp[i][1] += dp[i-1][0];
            if (f[i]+1!=lim[i]) dp[i][0] += dp[i-1][1];
        }
        printf("%lld
    ", dp[n-1][0]+dp[n-1][1]);
    }
    View Code

    ABC155F

    题意:$N$个炸弹,给定初始位置和状态,$M$根线,每根线连接一个区间的炸弹,切断的话会改变改变炸弹的状态,求怎样切能使炸弹状态全为$0$

    考虑炸弹状态的差分,添加$B[0]=B[N+1]=0$,那么全为$0$就等价于$[1,N+1]$的差分全为$0$

    考虑一根线$[L,R]$对差分序列的影响,会改变位置$L$和$R+1$的差分值

    那么问题就转化为给定一张无向图,要求保留一些边,使得每个连通块的异或和为$0$

    假设原图中存在一个连通块异或和非零,那么无论怎样删边一定无解

    假设所有连通块异或和均为零,那么一定有解,因为考虑一棵dfs树,回溯时如果当前点所处连通块状态为$0$就断开与父亲的边,这样最终转移到根时,根的状态一定为$0$

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e5+10;
    int n, m, val[N], b[N], vis[N];
    pair<int,int> a[N];
    vector<int> ans;
    vector<pair<int,int>> g[N];
    int dfs(int x) {
        vis[x] = 1;
        for (auto e:g[x]) { 
            int y = e.first;
            if (!vis[y]&&dfs(y)) val[x] ^= 1, ans.push_back(e.second);
        }
        return val[x];
    }
    int main() {
        scanf("%d%d", &n, &m);
        for (int i=1; i<=n; ++i) scanf("%d%d", &a[i].first, &a[i].second);
        sort(a+1,a+1+n);
        for (int i=1; i<=n+1; ++i) { 
            val[i] = a[i].second^a[i-1].second;
            b[i] = a[i].first;
        }
        for (int i=1; i<=m; ++i) {
            int l, r;
            scanf("%d%d", &l, &r);
            l = lower_bound(b+1,b+1+n,l)-b;
            r = upper_bound(b+1,b+1+n,r)-b;
            g[l].push_back({r,i});
            g[r].push_back({l,i});
        }
        for (int i=1; i<=n; ++i) if (!vis[i]) {
            if (dfs(i)) return puts("-1"),0;
        }
        sort(ans.begin(),ans.end());
        printf("%d
    ", (int)ans.size());
        for (int i=0; i<ans.size(); ++i) printf("%d%c", ans[i], " 
    "[i+1==ans.size()]);
    }
    View Code

    gym101667

    简单biset本来十几分钟就敲完了,结果被卡常,又改成NTT还是T,最后发现长度2e5改成1e5就过了

    #pragma GCC optimize("Ofast")
    #pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
    #pragma GCC optimize("unroll-loops")
    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e5+10;
    int n, m;
    char s[N], t[N];
    bitset<N> ch[3],a[3];
    int main() {
        scanf("%d%d%s%s", &n, &m, s+1, t+1);
        for (int i=1; i<=n; ++i) {
            if (s[i]=='S') s[i] = 'R';
            else if (s[i]=='P') s[i] = 'S';
            else s[i] = 'P';
        }
        for (int i=1; i<=n; ++i) {
            if (s[i]=='P') ch[0].set(i);
            else if (s[i]=='S') ch[1].set(i);
            else ch[2].set(i);
        }
        for (int i=1; i<=m; ++i) {
            if (t[i]=='P') a[0].set(i);
            else if (t[i]=='S') a[1].set(i);
            else a[2].set(i);
        }
        int ma = 0;
        for (int i=0; i<n; ++i) {
            int cnt = 0;
            for (int z=0; z<3; ++z) cnt += (a[z]&ch[z]>>i).count();
            ma = max(ma, cnt);
        }
        printf("%d
    ", ma);
    }
    View Code

    L

    题意:$p$个人,$p$个有向图,每个人初始在对应图点$1$,终点是对应图的点$s$,每个时刻一个人可以走一条边或者在一个点停留,走边和停留的花费给定,要求$p$个人同时出发,同时到达终点,求最少花费

    最优解对应时刻一定不超过$n1 imes n2 imes n3$,$dp$求出每个人每个时刻到达终点的花费就完了,证明暂时还不会

    K

    题意:走$n$步,给定每次走的距离和方向,求调整每次走的距离使得路径不交叉

    处理一下每步走的方向,如果$i+2$和$i$的方向相同,那么第$i$步走$1$,否则走$n-i+1$

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e4+10;
    int n, a[N], d[N];
    
    int main() {
        scanf("%d", &n);
        for (int i=2; i<=n; ++i) scanf("%*d%d", &a[i]);
        scanf("%*d%*d");
        if (n<=3) {
            for (int i=1; i<=n; ++i) printf("1%c", " 
    "[i==n]);
            return 0;
        }
        for (int i=2; i<=n; ++i) d[i] = (d[i-1]+a[i]+4)%4;
        printf("1 ");
        for (int i=2; i<n; ++i) {
            if (d[i]==d[i+2]) printf("1 ");
            else printf("%d ", n-i+1);
        }
        puts("1");
    }
    View Code

    G

    题意:给定两条折线段$L,U$,求下界为$L$上界为$U$的面积

    直接$n^2$可过,也可以写个二分或者双指针优化一下

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 3e5+10, M = 50010;
    int n, m;
    vector<int> x[2],y[2];
    int main() {
        scanf("%d%d", &n, &m);
        for (int i=1; i<=2*n+1; ++i) {
            int t;
            scanf("%d", &t);
            if (i&1) y[0].push_back(t);
            else x[0].push_back(t);
        }
        for (int i=1; i<=2*m+1; ++i) {
            int t;
            scanf("%d", &t);
            if (i&1) y[1].push_back(t);
            else x[1].push_back(t);
        }
        if (y[0][0]<y[0][1]&&y[1][0]>y[1][1]||y[0][0]>y[0][1]&&y[1][0]<y[1][1]) return puts("0 0"),0;
        if (y[0][0]>y[0][1]&&y[1][0]>y[1][1]) {
            for (int &t:y[0]) t = M-t;
            for (int &t:y[1]) t = M-t;
            swap(x[0],x[1]),swap(y[0],y[1]);
        }
        auto get = [&](int tp, int l, int r, int h) {
            for (int i=0; i+1<y[tp].size(); ++i) {
                int u = y[tp][i], v = y[tp][i+1];
                if (u<=h&&h<v) {
                    if (l<=x[tp][i]&&x[tp][i]<r) return i;
                    return -1;
                }
            }
            return -1;
        };
        auto calc = [&](int tp, int l, int r, int h) {
            int64_t ans = 0;
            for (int i=l+1; i<=r; ++i) {
                int p = i==x[tp].size()?M:x[tp][i];
                ans += (p-x[tp][i-1])*(int64_t)(y[tp][i]-h);
            }
            return ans;
        };
        int tot = 0;
        int64_t ans = 0;
        for (int i=0; i<x[0].size(); ++i) {
            int l = i==0?0:x[0][i-1], r = x[0][i], h = y[0][i];
            int posl = get(1,l,r,h);
            if (posl<0) continue;
            int posr;
            for (int j=posl; j<x[1].size(); ++j) {
                l = x[1][j], r = j+1==x[1].size()?M:x[1][j+1], h = y[1][j+1];
                posr = get(0,l,r,h);
                if (posr!=-1) {
                    ans += calc(1,posl,j+1,y[0][i])-calc(0,i,posr,y[0][i])-(r-x[0][posr])*(int64_t)(h-y[0][i]);
                    break;
                }
            }
            if (posr==-1) break;
            ++tot;
            i = posr;
        }
        printf("%d %lld
    ", tot, ans);
    }
    View Code

    A

    题意:给定树,要求给每个点赋一个非负权值$p$,使得对于每个权值为$0$的点$x$,存在一个权值为正的点$v$,满足$p_v$不小于$v$到$x$距离

    树形dp套路题,$p$为$0$的点看做白点,否则看做黑点

    设${dp}_{x,k,0}$为子树$x$内最远的白点距离$x$为$k$的最小值,${dp}_{x,k,1}$为子树$x$内最近的黑点还能从$x$往上延伸距离$k$的最小值

    维护子树大小$sz$

    对于${dp}_{x,k,1}$,当$k$超出${sz}_{x}$时也会有转移,但这样${dp}_{x,k,1}=k$,可以前缀优化

    对于${dp}_{y,k,1}$,当$k$超出${sz}_{y}$时,同理

    其他转移一定都在$sz$范围内,这样复杂度就是$O(n^2)$

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 5e3+10, INF = 0x3f3f3f3f;
    int n, sz[N], dp[N][N][2], tmp[N][2], suf[N][2];
    vector<int> g[N];
    void chkmin(int &a, int b) {a>b?a=b:0;}
    void dfs(int x, int f) {
        sz[x] = 1;
        dp[x][0][0] = 0;
        for (int u=1; u<=n; ++u) dp[x][u][1] = u;
        for (int y:g[x]) if (y!=f) { 
            dfs(y,x);
            for (int u=0; u<=n; ++u) { 
                tmp[u][0] = tmp[u][1] = suf[u][0] = suf[u][1] = INF;
            }
            for (int u=0; u<=sz[x]; ++u) {
                for (int v=0; v<=sz[y]; ++v) {
                    chkmin(tmp[max(u,v+1)][0],dp[x][u][0]+dp[y][v][0]);
                    if (u<=v-1) chkmin(tmp[v-1][1],dp[x][u][0]+dp[y][v][1]);
                    chkmin(tmp[u][0],dp[x][u][0]+dp[y][v][1]);
                    if (u>=v+1) chkmin(tmp[u][1],dp[x][u][1]+dp[y][v][0]);
                    chkmin(tmp[v+1][0],dp[x][u][1]+dp[y][v][0]);
                    chkmin(tmp[max(u,v-1)][1],dp[x][u][1]+dp[y][v][1]);
                }
                chkmin(suf[max(sz[y],u)][0],dp[x][u][0]+1);
            }
            int mi = suf[0][0];
            for (int v=0; v<=n; ++v) {
                chkmin(mi, suf[v][0]);
                chkmin(tmp[v][1], mi+v);
            }
            for (int v=0; v<=sz[y]; ++v) chkmin(suf[max(sz[x]+1,v+1)][1],dp[y][v][0]);
            mi = suf[1][1];
            for (int u=1; u<=n; ++u) {
                chkmin(mi, suf[u][1]);
                chkmin(tmp[u][1], mi+u);
            }
            sz[x] += sz[y];
            for (int u=0; u<=n; ++u) dp[x][u][0] = tmp[u][0], dp[x][u][1] = tmp[u][1];
            for (int u=1; u<=n; ++u) chkmin(dp[x][u][0], dp[x][u-1][0]);
            for (int u=n-1; u>=0; --u) chkmin(dp[x][u][1], dp[x][u+1][1]);
        }
    }
    
    int main() {
        scanf("%d", &n);
        if (n==1) return puts("1"), 0;
        for (int i=1; i<n; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            g[u].push_back(v);
            g[v].push_back(u);
        }
        memset(dp,0x3f,sizeof dp);
        dfs(1,0);
        printf("%d
    ", dp[1][0][1]);
    }
    View Code

    CF 1456B

    求最小操作数,关键要发现答案一定不大,因为连续三个数二进制最高位相同的话,那么异或后两个数后一定合法,所以$n>60$时答案一定为$1$,否则直接暴力

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e6+10;
    int n,a[N],s[N];
    void brute() {
        for (int i=1; i<=n; ++i) s[i]=s[i-1]^a[i];
        int ans = 1e9;
        for (int i=1; i<=n; ++i) {
            for (int j=1; j<i; ++j) {
                int w = s[i]^s[j-1];
                if (j>1&&w<a[j-1]||i<n&&w>a[i+1]) ans = min(ans, i-j);
            }
        }
        for (int l=1; l<=n; ++l) 
            for (int r=l+1; r<=n; ++r) 
                for (int u=r+1; u<=n; ++u) 
                    for (int v=u+1; v<=n; ++v) 
                        if ((s[r]^s[l-1])>(s[v]^s[u-1])) 
                            ans = min(ans, r-l+v-u);
        if (ans>n) ans = -1;
        printf("%d
    ", ans);
    }
    int main() {
        cin>>n;
        for (int i=1;i<=n;++i) cin>>a[i];
        if (n<=60) return brute(),0;
        puts("1");
    }
    View Code

    CF 1456C

    能重置$k$次,那么相当于进行$k+1$个独立的周目,然后考虑一种方案的贡献如何算

    就相当于是把$n$个数分配到k+1个数组中,假设一个数组分配的数是$x_0,x_1,x_2,x_3,...$,那么贡献就是$x_1+2x_2+3x_3+...$

    想让总和最大,考虑贪心,对于负数,想让它前面系数越小越好,对于正数就越大越好

    考虑最优解的结构,可以发现,一定是先从小到大逐层铺每个数,然后把剩余数全堆在最高的一列上,枚举这个分界点计算即可

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 5e5+10;
    int n, k, a[N];
    int64_t sum[N], f[N];
    int main() {
        scanf("%d%d", &n, &k);
        for (int i=1; i<=n; ++i) scanf("%d", &a[i]);
        sort(a+1,a+1+n,greater<int>());
        for (int i=1; i<=n; ++i) {
            f[i] = f[i-1]+sum[i-1];
            sum[i] = sum[i-1]+a[i];
        }
        int64_t ans = -1e18, ret = 0;
        int now = 0, cur = 0;
        for (int i=n; i>=1; --i) {
            ret += cur*(int64_t)a[i];
            ans = max(ans, ret+f[i-1]+(cur+1)*sum[i-1]);
            if (++now==k+1) ++cur, now = 0;
        }
        printf("%lld
    ", ans);
    }
    View Code

    CF 1456D

    转移有点繁的dp题,关键是要保证最优决策不能漏,花了一个多小时才写完

    设${dp}_{i,j}$表示时间为$t_i$时,是否能满足人在位置$x_i$,克隆在位置$x_j$

    但是如果直接这样$dp$的话,会发现先在位置$k$放克隆,然后走到位置$k+1$,等待位置$k$蛋糕拿到后,在位置$k+1$放克隆,然后直接往后走,这个时间是没法表示的,所以要在设一个$mi$

    ${mi}_i$表示当前人在位置$x_{i}$,克隆在位置$x_{i-1}$,等待克隆拿完$i-1$的蛋糕后,把克隆放到位置$i$的最短用时

    那么对于${dp}_{i,j}$有三种决策

    • 直接走到位置$x_{i+1}$,转移到${dp}_{i+1,j}$,需要满足$t_i+|x_i-x_{i+1}|le t_{i+1}$
    • 把克隆放在位置$k$,然后走到位置$i+1$,转移到${dp}_{i+1,k}$,需要满足$t_i+|x_i-x_k|+|x_k-x_{i+1}|le t_{i+1}$
    • 把克隆放在位置$i+1$,然后走到位置$i+2$等待放克隆。如果$j=i+1$的话,$i+1$是没必要去的,还要考虑把克隆放在位置$k$,然后走回$i+2$的情况

    对于${mi}_i$,${mi}_{i}$转移时首先要满足${mi}_{i}le t_{i}$,有两种决策

    • 把克隆放在位置$k$,然后走到位置$i+1$,转移到${dp}_{i+1,k}$
    • 走到$i+1$放克隆,转移到${mi}_{i+1}$
    #include <bits/stdc++.h>
    using namespace std;
    const int N = 5e3+10;
    int n, t[N], x[N], mi[N];
    bool dp[N][N];
    int main() {
        scanf("%d", &n);
        for (int i=1; i<=n; ++i) scanf("%d%d", &t[i], &x[i]);
        if (abs(x[1])>t[1]) return puts("NO"),0;
        for (int i=1; i<=n; ++i) if (abs(x[i])+abs(x[i]-x[1])<=t[1]) dp[1][i] = 1;
        memset(mi,0x3f,sizeof mi);
        mi[1] = abs(x[1]);
        for (int i=1; i<n; ++i) {
            int f = 1;
            for (int j=1; j<=n; ++j) {
                if (!dp[i][j]) continue;
                if (t[i]+abs(x[i]-x[i+1])<=t[i+1]) dp[i+1][j] = 1;
                if (f) {
                    for (int k=i+1; k<=n; ++k)
                        if (t[i]+abs(x[i]-x[k])+(int64_t)abs(x[k]-x[i+1])<=t[i+1]) 
                            dp[i+1][k] = 1;
                    f = 0;
                }
                if (j==i+1) { 
                    mi[i+2] = min(mi[i+2], max(t[i+1],t[i]+abs(x[i]-x[i+2])));
                    for (int k=i+1; k<=n; ++k) 
                        if (max(t[i+1],t[i]+abs(x[i]-x[k]))+(int64_t)abs(x[k]-x[i+2])<=t[i+2]) 
                            dp[i+2][k] = 1;
                }
                else if (t[i]+abs(x[i]-x[i+1])<=t[i+1]) mi[i+2] = min(mi[i+2], (int)max((int64_t)t[i+1],t[i]+abs(x[i]-x[i+1])+(int64_t)abs(x[i+1]-x[i+2])));
            }
            if (mi[i]>t[i]) continue;
            for (int k=i+1; k<=n; ++k) if (max(t[i],mi[i]+abs(x[k]-x[i]))+(int64_t)abs(x[k]-x[i+1])<=t[i+1]) dp[i+1][k] = 1;
            if (mi[i]+abs(x[i]-x[i+1])<=t[i+1]) mi[i+1] = min(mi[i+1], max(t[i],mi[i]+abs(x[i]-x[i+1])));
        }
        if (mi[n]<=t[n]||dp[n-1][n]) puts("YES"),exit(0);
        for (int i=1; i<=n; ++i) if (dp[n][i]) puts("YES"),exit(0);
        puts("NO");
    }
    View Code
  • 相关阅读:
    angular面试记忆的内容
    doctype
    161214、oracle查询表信息
    161213、Maven资源替换和Freemarker模板
    161212、并发编程中的关于队列
    161209、简要分析ZooKeeper基本原理及安装部署
    161208、Java enum 枚举还可以这么用
    161207、高并发:java.util.concurrent.Semaphore实现字符串池及其常用方法介绍
    161206、 Ionic、Angularjs、Cordova搭建Android开发环境
    161205、win10安装mysql5.7.16数据库
  • 原文地址:https://www.cnblogs.com/fs-es/p/14035095.html
Copyright © 2020-2023  润新知