• BZOJ整理


    题解摘自各个博客(来源也不是很想复制了,懒人一枚),自己不想打了,稍作补充

     

    4985: 评分

    Time Limit: 10 Sec  Memory Limit: 64 MB

    Description

    Lj最近参加一个选秀比赛,有N个评委参加了这次评分,N是奇数。评委编号为1到N。每位评委给Lj打的分数是一个
    整数,评委i(1 ≦ i ≦ N)的打分为Di。这次采用了一种创新的方法计算最后得分,计算规则是:最初N位评委排
    成一排,检查队伍排头的3位评委的评分,去掉一个最高分和一个最低分,剩下的一个评委移动到队伍最后,反复
    执行以上操作,直到队伍中的评委只剩一位,那么这个评委的打分就是Lj的最后得分。由于有的评委年纪比较大了
    ,不记得自己的位置了,现在有M(1 ≦ M ≦ N - 2)个评委很快找到了自己的位置,剩下的N-M人找不到位置了,
    需要给他们重新安排位置。
    由于Lj希望自己的得分尽可能高。请你帮忙计算出LJ最后得分可能的最大值。

    Input

    第一行为整数N和M,用空格分隔。表示有N位评委,其中M人的初始排列位置已经确定。
    接下来M行中第i行(1 ≦ i ≦ M)为两个整数Di和Pi,用空格分隔。
    表示第i位评委的评分为Di,初始排列位置为队伍排头开始的第Pi位。
    接下来N-M行中第i行(1 ≦ i ≦ N ? M)为整数Di+M,表示评委(i+M)的评分为Di+M。
    3 ≦ N ≦ 99 999,
    1 ≦ M ≦ N - 2,
    1 ≦ Di ≦ 109 (1 ≦ i ≦ N),
    1 ≦ Pi ≦ N (1 ≦ i ≦ M),
    Pi != Pj (1 ≦ i < j ≦ M)。

    Output

     输出一行,为1个整数,表示LJ得分的最大值。

    Sample Input

    7 3
    5 2
    5 5
    8 6
    6
    2
    8
    9
     
    题解:二分+DP,非常神奇的解法;这题可能开始会想到贪心,但是发现次数多了是行不通的
     

    首先二分一个答案x,然后我们把>=x的数看成1,<x的数看成0,那如果最后剩下1,这个答案就是合法的。

    那我们就来算让某一位得1至少需要填几个1(设这个值是f[i])

    i=1..n时,显然,如果i已经固定,f[i]=0或inf(取决于原来是1还是0);如果i还没有固定,那f[i]=1

    然后每次就可以由前三个转移到最后一个,也就是取这三个中f[i]较小的两个相加(转移过去的是1,当且仅当3个里有至少2个1)

    这个转移和队列很像,所以可以直接用队列维护。

    最后我们看f[最后那位数]是否多于还没填的1的数量就完事了。

    #include<bits/stdc++.h>
    using namespace std;
    const int ME = 200005, M = 1e5 + 5;
    queue <int> q;
    int inf = 1e9, f[M], m, sc[M], d[M], n, cc[M];
    bool check(int x){
        int tot = 0;
        for(int i = 1; i <= n-m; i++) tot += cc[i] >= x;
        for(int i = 1; i <= n; i++){
            if(sc[i]) f[i] = sc[i] >= x ? 0 : inf;
            else f[i] = 1;
            q.push(f[i]);
        } 
        while(!q.empty()){
            int a=q.front();q.pop();
            if(q.empty()) return a <= tot;
            int b=q.front();q.pop();
            int c=q.front();q.pop();
            q.push(min(inf, min(a+b, min(b+c, a+c))));
        }
    }
     
     
    int main(){
        int p;
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= m; i++)
            scanf("%d%d", &d[i], &p), sc[p] = d[i];
        for(int i = m+1; i <= n; i++) scanf("%d", &d[i]), cc[i-m] = d[i];
        sort(d+1, d+1+n);
        int ans, lf = 1, rg = n;
        while(lf <= rg){
            int mid = (lf + rg) >> 1;
            if(check(d[mid])) ans = d[mid], lf = mid + 1;
            else rg = mid - 1;
        }
        printf("%d
    ", ans);
    }
    View Code

    2131: 免费的馅饼

    Time Limit: 10 Sec  Memory Limit: 259 MB

    Description

    Input

    第一行是用空格隔开的二个正整数,分别给出了舞台的宽度W(1到10^8之间)和馅饼的个数n(1到10^5)。  接下来n行,每一行给出了一块馅饼的信息。由三个正整数组成,分别表示了每个馅饼落到舞台上的时刻t[i](1到10^8秒),掉到舞台上的格子的编号p[i](1和w之间),以及分值v[i](1到1000之间)。游戏开始时刻为0。输入文件中同一行相邻两项之间用一个空格隔开。输入数据中可能存在两个馅饼的t[i]和p[i]都一样。

    Output

    一个数,表示游戏者获得的最大总得分。

    Sample Input

    3 4
    1 2 3
    5 2 3
    6 3 4
    1 1 5

    Sample Output

    12
    【数据规模】
    对于100%的数据,1<=w,t[i]<=10^8,1<=n<=100000。

    题解:两个要点:一是把时间增倍,可以看成走一步,休息一步;

    二是一个二维偏序的转移

    一道经典的二维偏序问题。至于怎么将原题目转换为二维偏序??

    首先可以将每秒走一步和两步转换为在一秒钟走一步或半步,即把时间加倍。接下来考虑dp转移。能从j转移到i当且仅当ti-tj>=|pi-pj|,可以转换为两个式子:pi>=pj时,ti-pi>=tj-pj,又因为pi-pj此时是正数,所以pj-pi是负数,因为ti-tj此时已经大于一个正数,则它也一定大于负数,即ti-tj>=pj-pi也成立,即ti+pi>=tj+pj一定成立,同理pi<pj时,ti+pi>=tj+pj,ti-pi>=tj-pj也一定成立。所以满足条件的转移一定满足这两个式子。而满足这两个式子时,ti-tj一定是个正数。所以不用考虑ti的顺序了。

    设val1=ti+pi,val2=ti-pi,则转换为了一个二维偏序问题。一维排序,一维用值域树状数组或者值域线段树优化。【注意】因为t值非常大,需要离散化值域

    #include<bits/stdc++.h>
    using namespace std;
    const int M = 1e5 + 5;
    int w, n, lim, ls[M], c[M];
     
    struct Cake{
        int x, y, v;
    }s[M];
    bool cmp(Cake a, Cake b){
        return a.x < b.x;
    }
    int query(int x){
        int ret = 0;
        for(; x; x -= x&(-x)) ret = max(ret, c[x]);
        return ret;
    }
    void update(int x, int v){
        for(; x <= lim; x += x&(-x)) c[x] = max(c[x], v);
    }
     
    int main(){
        int p, t, v;
        scanf("%d%d", &w, &n);
        for(int i = 1; i <= n; i++){
            scanf("%d%d%d", &t, &p, &v);
            s[i].x = 2*t + p;
            s[i].y = 2*t - p;
            s[i].v = v;
            ls[i] = s[i].y;
        }
        sort(s + 1, s + 1 + n, cmp);
        sort(ls + 1, ls + 1 + n);
        lim = unique(ls + 1, ls + 1 + n) - ls - 1;
        for(int i = 1; i <= n; i++){
            int pos = lower_bound(ls + 1, ls + 1 + lim, s[i].y) - ls;
            int now = query(pos) + s[i].v;
            update(pos, now);
        }
        int ans = query(lim);
        printf("%d
    ", ans);
         
    }
    View Code

    1975: [Sdoi2010]魔法猪学院

    Time Limit: 10 Sec  Memory Limit: 64 MB

    Description

    iPig在假期来到了传说中的魔法猪学院,开始为期两个月的魔法猪训练。经过了一周理论知识和一周基本魔法的学习之后,iPig对猪世界的世界本原有了很多的了解:众所周知,世界是由元素构成的;元素与元素之间可以互相转换;能量守恒……。 能量守恒……iPig 今天就在进行一个麻烦的测验。iPig 在之前的学习中已经知道了很多种元素,并学会了可以转化这些元素的魔法,每种魔法需要消耗 iPig 一定的能量。作为 PKU 的顶尖学猪,让 iPig 用最少的能量完成从一种元素转换到另一种元素……等等,iPig 的魔法导猪可没这么笨!这一次,他给 iPig 带来了很多 1 号元素的样本,要求 iPig 使用学习过的魔法将它们一个个转化为 N 号元素,为了增加难度,要求每份样本的转换过程都不相同。这个看似困难的任务实际上对 iPig 并没有挑战性,因为,他有坚实的后盾……现在的你呀! 注意,两个元素之间的转化可能有多种魔法,转化是单向的。转化的过程中,可以转化到一个元素(包括开始元素)多次,但是一但转化到目标元素,则一份样本的转化过程结束。iPig 的总能量是有限的,所以最多能够转换的样本数一定是一个有限数。具体请参看样例。

    Input

    第一行三个数 N、M、E 表示iPig知道的元素个数(元素从 1 到 N 编号)、iPig已经学会的魔法个数和iPig的总能量。 后跟 M 行每行三个数 si、ti、ei 表示 iPig 知道一种魔法,消耗 ei 的能量将元素 si 变换到元素 ti 。

    Output

    一行一个数,表示最多可以完成的方式数。输入数据保证至少可以完成一种方式。

    Sample Input

    4 6 14.9
    1 2 1.5
    2 1 1.5
    1 3 3
    2 3 1.5
    3 4 1.5
    1 4 1.5

    Sample Output

    3

    HINT

    样例解释
    有意义的转换方式共4种:
    1->4,消耗能量 1.5
    1->2->1->4,消耗能量 4.5
    1->3->4,消耗能量 4.5
    1->2->3->4,消耗能量 4.5
    显然最多只能完成其中的3种转换方式(选第一种方式,后三种方式仍选两个),即最多可以转换3份样本。
    如果将 E=14.9 改为 E=15,则可以完成以上全部方式,答案变为 4。

    数据规模
    占总分不小于 10% 的数据满足 N <= 6,M<=15。
    占总分不小于 20% 的数据满足 N <= 100,M<=300,E<=100且E和所有的ei均为整数(可以直接作为整型数字读入)。
    所有数据满足 2 <= N <= 5000,1 <= M <= 200000,1<=E<=107,1<=ei<=E,E和所有的ei为实数。

    题解:贪心+A*,相当于先走最短的;

    #include<bits/stdc++.h>
    using namespace std;
    const int ME = 200005, M = 5005;
    int n, m;
    int tot, tot1, h[M], hh[M];
    bool inq[M];
    struct edge{int v, nxt;double w;}G[ME], g[ME];
    void add(int u, int v, double w){G[++tot].v = v, G[tot].w = w, G[tot].nxt = h[u], h[u] = tot;}
    void add_op(int u, int v, double w){g[++tot1].v = v, g[tot1].w = w, g[tot1].nxt = hh[u], hh[u] = tot;}
    double dis[M], E, inf = 2e9;
    queue<int> Q;
    void spfa(){
        for(int i=1;i<=n;i++)dis[i]=inf;
        Q.push(n);inq[n]=1;dis[n]=0;
        while(!Q.empty()){
            int u=Q.front();Q.pop();inq[u]=0;
            for(int i=hh[u];i;i=g[i].nxt){
                int v=g[i].v;
                if(dis[v] > dis[u]+g[i].w){
                    dis[v] = dis[u]+g[i].w;
                    if(!inq[v])inq[v]=1, Q.push(v);
                }
            }
        }
    }
    struct node{
        int v; double f;
        bool operator < (const node &rhs)const{
            return f + dis[v] > rhs.f + dis[rhs.v];
        }
    };
    priority_queue<node> q;
     
    int Astar(){
        int k = 0;
        q.push((node){1, 0});
        while(!q.empty()){
            node u=q.top();q.pop();
            if(u.v == n){
                if(u.f > E)break;
                E -= u.f; k++;
            }
            for(int i=h[u.v];i;i=G[i].nxt){
                int v=G[i].v;
                q.push((node){v, u.f + G[i].w});
            }       
        }
        return k;
    }
     
    int main(){
        int u, v; double w;
        scanf("%d%d%lf",&n, &m, &E);
        for(int i = 1; i <= m; i++){
            scanf("%d%d%lf", &u, &v, &w);
            add(u, v, w);
            add_op(v, u, w);
        }
        spfa();
        int ans = Astar();
        printf("%d
    ", ans);
         
    }

    3697: 采药人的路径

    Time Limit: 10 Sec  Memory Limit: 128 MB

    Description

    采药人的药田是一个树状结构,每条路径上都种植着同种药材。
    采药人以自己对药材独到的见解,对每种药材进行了分类。大致分为两类,一种是阴性的,一种是阳性的。
    采药人每天都要进行采药活动。他选择的路径是很有讲究的,他认为阴阳平衡是很重要的,所以他走的一定是两种药材数目相等的路径。采药工作是很辛苦的,所以他希望他选出的路径中有一个可以作为休息站的节点(不包括起点和终点),满足起点到休息站和休息站到终点的路径也是阴阳平衡的。他想知道他一共可以选择多少种不同的路径。

    Input

    第1行包含一个整数N。
    接下来N-1行,每行包含三个整数a_i、b_i和t_i,表示这条路上药材的类型。

    Output

    输出符合采药人要求的路径数目。

    Sample Input

    7
    1 2 0
    3 1 1
    2 4 0
    5 2 0
    6 3 1
    5 7 1

    Sample Output

    1

    HINT

    对于100%的数据,N ≤ 100,000。

    题解:一道好题,一个思维与码力兼具的题

    来自出题人hta的题解。。

    本题可以考虑树的点分治。问题就变成求过根满足条件的路径数。

    路径上的休息站一定是在起点到根的路径上,或者根到终点的路径上。

    如何判断一条从根出发的路径是否包含休息站?只要在dfs中记录下这条路径的和x,同时用个标志数组判断这条路径是否存在前缀和为x的节点。

    这样我们枚举根节点的每个子树。用f[i][0…1],g[i][0…1]分别表示前面几个子树以及当前子树和为i的路径数目,0和1用于区分路径上是否存在前缀和为i的节点。那么当前子树的贡献就是f[0][0] * g[0][0] + Σf [i][0] * g [-i][1] + f[i][1] * g[-i][0] + f[i][1] * g[-i][1],其中i的范围[-d,d],d为当前子树的深度。

    #include<bits/stdc++.h>
    using namespace std;
    #define ex(i, u) for(int i = h[u]; i; i = G[i].nxt)
    #define ll long long
    const int M = 100005, N = M;
    int h[M], dep[M], tot, dis[M], f[M << 1][2], g[M << 1][2], siz[M], son[M], tmp[M], root, sum, ap[N*2], mxdep;
    ll Ans;
    bool vis[M];
    int read(){
        int x=0,f=1;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
        while(c<='9'&&c>='0'){x=x*10+c-'0';c=getchar();}
        return x*=f;
    }
     
    struct edge{int v, w, nxt;}G[M << 1];
    void add(int u, int v, int w){G[++tot].v = v, G[tot].w = w, G[tot].nxt = h[u], h[u] = tot;}
    void getroot(int u, int f){
        siz[u] = 1; son[u] = 0;
        ex(i, u){
            int v = G[i].v;
            if(vis[v] || v == f)continue;
            getroot(v, u);
            siz[u] += siz[v];
            if(siz[son[u]] < siz[v]) son[u] = v;
        }
        tmp[u] = max(siz[son[u]], sum - siz[u]);
        if(tmp[u] < tmp[root]) root = u;
    }
    void getdeep(int u, int f){
        mxdep = max(mxdep ,dep[u]);
        if(ap[dis[u]])g[dis[u]][1]++;
        else g[dis[u]][0]++;
        ap[dis[u]]++;
         
        ex(i, u){
            int v = G[i].v;
            if(v == f || vis[v])continue;
            dis[v] = dis[u] + G[i].w;
            dep[v] = dep[u] + 1;
             
            getdeep(v, u);
             
        }
        ap[dis[u]]--;
    }
     
     
    ll cal(int u, int kk){
        dep[u] = 1; dis[u] = kk + N;
        ll ret = 0;  mxdep = 1; f[N][0] = 1;
         
        ex(i, u){
            int v = G[i].v;
            if(vis[v])continue;
            dis[v] = dis[u] + G[i].w;
            dep[v] = dep[u] + 1;
            getdeep(v, u);
            ret += g[N][0] * (f[N][0] - 1);
            for(int j = -mxdep; j <= mxdep; j++)
                ret += f[N + j][0] * g[N - j][1] + f[N + j][1] * (g[N - j][1] + g[N - j][0]);
                //printf("%d %d
    ", j, ret);        
            for(int j = N - mxdep; j <= N + mxdep; j++){
                f[j][0] += g[j][0], f[j][1] += g[j][1];
                g[j][0] = g[j][1] = 0; 
            }
        }
        for(int i = N - mxdep; i <= N + mxdep; i++)f[i][0] = f[i][1] = 0;
        return ret;
    }
     
     
     
    void dfs(int u){
        Ans += cal(u, 0);
        //printf("+ %I64d %d
    ", Ans, u);
        vis[u] = 1;
        ex(i, u){
            int v = G[i].v;
            if(vis[v])continue;
            //Ans -= cal(v, G[i].w);
            //printf("- %I64d %d
    ", Ans, v);
            sum = siz[v];
            getroot(v, root = 0);
             
            dfs(root);
        }
    }
     
     
     
     
    int main(){
        //freopen("data.out","r",stdin);
        //freopen("my.out","w",stdout);
        //int tt = clock();
        int n = read();
        int u, v, w;
        for(int i = 1; i < n; i++){
            u = read(), v = read(), w = read();
            w = w ? 1 : -1;
            add(u, v, w), add(v, u, w);
        }   
        tmp[0] = 1e9;
        sum = n;
        getroot(1, 0);
        dfs(root);
        printf("%lld
    ", Ans);
        //int cc = clock();
        //cout<<cc-tt;
    }
    View Code

    4565: [Haoi2016]字符合并

    Time Limit: 20 Sec  Memory Limit: 256 MB

    Description

    有一个长度为 n 的 01 串,你可以每次将相邻的 k 个字符合并,得到一个新的字符并获得一定分数。得到的新字
    符和分数由这 k 个字符确定。你需要求出你能获得的最大分数。

    Input

    第一行两个整数n,k。接下来一行长度为n的01串,表示初始串。接下来2k行,每行一个字符ci和一个整数wi,ci
    表示长度为k的01串连成二进制后按从小到大顺序得到的第i种合并方案得到的新字符,wi表示对应的第i种方案对应
    获得的分数。1<=n<=300,0<=ci<=1,wi>=1,k<=8

    Output

    输出一个整数表示答案

    Sample Input

    3 2
    101
    1 10
    1 10
    0 20
    1 30

    Sample Output

    40
    //第3行到第6行表示长度为2的4种01串合并方案。00->1,得10分,01->1得10分,10->0得20分,11->1得30分。

    题解:困难的区间状压DP,目前应该可以放一放

    发现一个区间的最大分数一定是压缩到最短的时候,这个时候的长度是(modk−1)(modk−1)下的长度,设f[l][r][o]f[l][r][o]表示[l,r][l,r]区间压缩为oo状态的最大分数,那么转移只需考虑把原始区间分为两段,两段拼接成oo状态就好了。

    但是如何DP?有一种思路是直接枚举区间与状态,然后两段的状态随之确定。但是有一个问题是可能是两段的状态合并之后再压缩成当前的状态。其实仔细分析根本不用考虑合并的状态,只需把转移锁定到状态的最后一位即可:

    1.若该区间压缩后的长度不为1,那么该区间的最后状态o1o1的最后一位一定是由该区间末尾的某一段压缩而成。直接枚举是哪一段就好了。

    2.若该区间压缩后的长度为1,那么考虑枚举长度为k的状态,之后再枚举一遍每种状态压缩后的分数,取最大值即可。

    #include<bits/stdc++.h>
    using namespace std;
    const int M = 305,  N = (1 << 8) + 1;
    #define ll long long
    int a[M], to[N];
    ll w[N], dp[M][M][N], g[3];
    const ll inf = -1e9;
     
     
    int main(){
        int n, k;
        scanf("%d%d", &n, &k);
        for(int i = 1; i <= n; i++)scanf("%1d", &a[i]);
        for(int s = 0; s < (1 << k); s++)scanf("%d%lld", &to[s], &w[s]);
        memset(dp, 0x8f, sizeof(dp));
        for(int i = 1; i <= n; i++)dp[i][i][a[i]] = 0;
         
        for(int L = 2; L <= n; L++)
            for(int i = 1; i <= n - L + 1; i++){
                int j = i + L - 1, len = j - i;
                while(len > k - 1) len -= (k - 1);
                 
                for(int mid = j; mid > 0; mid -= k-1){
                    for(int s = 0; s < (1 << len); s++)
                    if(dp[i][mid - 1][s] > inf){
                        if(dp[mid][j][1] > inf) 
                            dp[i][j][s<<1|1] = max(dp[i][j][s<<1|1], dp[i][mid - 1][s] + dp[mid][j][1]);
                        if(dp[mid][j][0] > inf) 
                            dp[i][j][s<<1] = max(dp[i][j][s<<1], dp[i][mid - 1][s] + dp[mid][j][0]);
                        //printf("%d %d %d %d %I64d %I64d
    ",len,i, j, s, dp[i][j][s<<1|1], dp[i][j][s<<1]);
                    }   
                }
                if(len == k-1){
                        g[0] = g[1] = inf;
                        for(int s = 0; s < (1 << k); s++)
                            if(dp[i][j][s] > inf)
                                g[to[s]] = max(g[to[s]], dp[i][j][s] + w[s]);
                        dp[i][j][1] = g[1]; dp[i][j][0] = g[0];
                        //printf("%d %d %I64d %I64d    twice
    ",i, j, dp[i][j][0], dp[i][j][1]);
                    }
     
            }
         
        ll ans = inf;
        for(int s = 0; s < (1 << k); s++)
            ans = max(ans, dp[1][n][s]);
        printf("%lld
    ", ans);
    }
    View Code
  • 相关阅读:
    面向对象进阶
    20191011作业
    2019.10.10作业
    类的继承
    面向对象
    2019.10.09作业
    pandas模块
    [BZOJ 2190][SDOI2008]仪仗队(欧拉函数)
    [BZOJ 2729][HNOI2012]排队(组合数学+高精)
    [BZOJ 1491][NOI2007]社交网络(Floyd)
  • 原文地址:https://www.cnblogs.com/EdSheeran/p/9778635.html
Copyright © 2020-2023  润新知