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\n", 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\n", 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\n", 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\n", 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\n", 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\n", 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\n", 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\n",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\n",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\n", ans);
}
View Code

 

 

posted @ 2018-10-12 15:57  Ed_Sheeran  阅读(271)  评论(0)    收藏  举报