zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    Mvc+三层(批量添加、删除、修改)
    js中判断复选款是否选中
    EF的优缺点
    Git tricks: Unstaging files
    Using Git Submodules
    English Learning
    wix xslt for adding node
    The breakpoint will not currently be hit. No symbols have been loaded for this document."
    Use XSLT in wix
    mfc110ud.dll not found
  • 原文地址:https://www.cnblogs.com/EdSheeran/p/9778635.html
Copyright © 2011-2022 走看看