2017/9/14总结

来源:互联网 发布:淘宝双十一实时数据 编辑:程序博客网 时间:2024/06/05 10:12
这周就是补题
 主要是上星期天沈阳的网络赛有点崩,心态炸了,拿出时间来补补题,明天再继续a题。
告诉你一个字符串和k , 求这个字符串中有多少不同的子串恰好出现了k 次。
下一个是电缆那个题
【题意】
从1到n共计n(90)个物品,每个物品有一个价值a[]

小孩先手。小孩爸爸轮流做游戏。
爸爸每次可以取最左边或最右边的物品。
小孩每次选价值最大的{最左边,最右边}的物品,如果价值一样大, 则选取最左边的物品。

问你,爸爸想要输(价格严格小),而且差值尽可能少的最小差值是多少。

【分析】
首先,我们DP两个东西——
1, mx[l][r]表示对于区间[l, r],儿子先手,爸爸所能拿到的最大价值差值(差值是爸爸减儿子)
2, mn[l][r]表示对于区间[l, r],儿子先手,爸爸所能拿到的最小价值差值(差值是爸爸减儿子)



那么——
我们尝试使用搜索解决这个问题

dfs(l, r, dif)表示当前还没有取的区间范围是[l, r],儿子先手,此时爸爸减儿子的差值为dif。

那么——
1,这时先考虑剪枝——

设置初始ANS = -inf;

void dfs(int l, int r, int dif)
{
if (dif + mn[l][r] >= 0) return;//哪怕取一个最小值,都会赢了儿子,是个无效状态
if (dif + mx[l][r] <= ANS) return;//哪怕取一个最大值,差值都依然太小了,最优性剪枝
if (dif + mx[l][r] < 0)//取一个最大值,使得差值尽可能小,最优性剪枝
{
gmax(ANS, dif + mx[l][r]);
}
}

2,再模拟儿子的操作,获得新的(l, r, dif)

3,接着需要进一步地考虑爸爸的操作——
<1>取l,变成dfs(l + 1, r, dif + a[l]);
<2>取r,变成dfs(l, r - 1, dif + a[r]);

4,考虑如何获得DP数组mn[][]和mx[][]——

mn[i][i - 1] = mx[i][i - 1] = 0;

for(int l = n; l >= 1; --l)
{
for(int r = l; r <= n; ++r)
{
先模拟儿子的操作,获得新的(ll, rr)
然后考虑父亲的操作——
1,取ll:
gmax(mx[l][r], a[ll] + mx[ll + 1][rr]);
gmin(mn[l][r], a[ll] + mn[ll + 1][rr]);
2,取rr:
gmax(mx[l][r], a[rr] + mx[ll][rr - 1]);
gmin(mn[l][r], a[rr] + mn[ll][rr - 1]);

}
}

题目的意思是给出一个序列问能否去掉k的数之后使得整个序列不是递增也不是递减的
思路:先求出LIS,然后倒序求出最长递减子序列长度,然后判断去k的数后长度是否都大于所求长度

#include <bits/stdc++.h>
using namespace std;

int a[100005];
int b[100005];
int jl[100005];
int n,m,T;

int main()
{
for(scanf("%d",&T);T--;)
{
    scanf("%d %d",&n,&m);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
    }
    jl[0] = a[0];
    int len = 1;
    int tj;
    for(int i = 1; i < n; i++)
    {
        if(jl[len - 1] <= a[i])
        {
            jl[len++] = a[i];
        }
        else
        {
            tj = upper_bound(jl, jl + len, a[i]) - jl;
            jl[tj] = a[i];
        }
    }
    int al = len;
    for(int i = 0; i < n; i++)
        b[i] = a[n - i];
    jl[0] = b[0];
    len = 1;
    for(int i = 1; i < n; i++)
    {
        if(jl[len - 1] <= b[i])
        {
            jl[len++] = b[i];
        }
        else
        {
            tj = upper_bound(jl, jl + len, b[i]) - jl;
            jl[tj] = b[i];
        }
    }
    int bl = len;
    if(n - m > al && n - m > bl)
        printf("A is not a magic array.\n");
    else
        printf("A is a magic array.\n");
}
  return 0;


——


题目的意思是在斐波那契序列中找出k个数从凑出的数是good的,不能就是bad的,给出k,求最小的bad数
思路:找规律发现这个数是斐波那契第2*k+3项-1,数据较大矩阵快速幂搞定

6199

给定一个n个点m条边无向图(n,m<=1e5)
  支持两个操作
    1.添加一条边
    2.询问点u到点v的所有路径中必经边的条数
  操作数<=1e5
分析
  第一眼看起来像是要动态维护无向图的边双连通分量,看似无法做
  其实可以这样想,假设我们有一个树,那么这个树上的每一条边都是必经边
  现在假设多加一条非树边(u,v),那么相当于原树上u->v这条路径上的所有边都变成了非必经边
  也就是如果我们认为必经是1,非必经是0,那么所要做的两个操作就是1.将树上路径全部赋为0    2.询问树上路径的和
  这是裸的树链剖分,但可惜的是这题10组数据,卡了$O(nlog^2n)$的树链剖分
  这题就要用神奇的套路解决了
  注意到这里树上路径操作只是将1变成0,所以有效操作最多就n次,树链剖分中有很多没必要的赋值
  我们可以用并查集来解决,记录f[i]表示点i的上一个仍旧是1的点,这样对于修改直接利用并查集在树上跳,这样修改是$O(n)$的
  那么怎么处理询问呢
  我们可以维护每个点到根节点路径上权值和,那么询问的时候加一加减一减就行了,现在考虑如果将一条边u-fa[u]从1改成0,那么相当于以u为根的子树中所有的权值都要减去1
  所以这个我们可以先dfs序,然后相当于“改段求点”,直接树状数组就ok了


题意:
给你一棵树, 树上有点权, 要求选择起点S和终点T, 要求T-S-sum 最大, sum为S到T的边权。
思路:
根据题意就可以建图
建立源点和汇点。
源点连所有的树上点, 边权为 a[i], 所有树上点在连接 汇点, 边权为-a[i]. 然后在根据树建图。 
spfa跑个最长路即可。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
int T;

const int maxn = 100000 + 10;

int d[maxn];
int a[maxn];
struct Edge{
    int u, v, d;
    Edge(int u = 0,int v = 0,int d = 0):u(u), v(v), d(d){}
};
vector<Edge>edges;
int n;
vector<int>g[maxn];
void add(int u,int v,int w){
    edges.push_back(Edge(u, v, w));
    g[u].push_back((int)edges.size() - 1);
}
bool vis[maxn];
int cnt[maxn];
queue<int>q;
void spfa(){
    memset(vis,0,sizeof vis);
    memset(cnt,0,sizeof cnt);
    while(!q.empty())q.pop();
    vis[0] = 1; cnt[0] = 1;
    memset(d,0,sizeof d);
//    d[0] = 0;
    q.push(0);
    while(!q.empty()){
        int u = q.front(); q.pop();
        vis[u] = 0;
        for (int i = 0; i < g[u].size(); ++i){
            int id = g[u][i];
            int v = edges[id].v;
            int w = edges[id].d;
            if (d[v] < d[u] + w){
                d[v] = d[u] + w;
                if (!vis[v]){
                    vis[v] = 1;
                    cnt[v]++;
                    q.push(v);
                    if (cnt[v] > n) return;
                }
            }
        }
    }
    printf("%d\n", d[n+1]);
}
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        edges.clear();
        for (int i = 1; i <= n; ++i){
            scanf("%d", &a[i]);
            g[i].clear();
        }

        for (int i = 1; i <= n; ++i){
            add(0, i, a[i]);
            add(i, n+1, -a[i]);
        }
        for (int i = 1; i < n; ++i){
            int u, v, w;
            scanf("%d %d %d", &u, &v, &w);
            add(u, v, -w);
            add(v, u, -w);
        }
        spfa();
    }

    return 0;
}

题目链接:HDU 6203

题意就是求最少的点覆盖所有给定的q条路径,其实可以转换一下,就是求最多的路径集合使得集合内的路径两两之间不存在交点,那么跟HDU 4912是一样的题目了,先把所有路径按照其LCA深度从大到小排,即先处理靠下方的,因为这样对其他路径影响肯定是最小的,然后看这条路径的两端u和v是否被标记过,如果没标记就把以LCA(u,v)为根的子树全部标记上,否则就说明这条路径至少与其他路径存在一个交点(被标记的点)以此类推,如何快速标记子树呢?DFS序啊,而且子树是区间更新,查询端点是点查询,用BIT刚刚好

6205
题意:求通过把最前面的一堆牌翻转到最后满足能把所有的牌的数目都取到的最少反转次数

思路:根据题意,牌的数量和消耗的费用是相同的,所以总有一种顺序能够取到所有的牌:
我们首先从顺序头开始扫描求和,出现和为负的情况时做一个标记,然后和重置为0,继续向后扫描,最后把所有和为负的长度相加就是需要反转的次数


告诉你一个字符串和k , 求这个字符串中有多少不同的子串恰好出现了k 次。
下一个是电缆那个题
【题意】
从1到n共计n(90)个物品,每个物品有一个价值a[]
小孩先手。小孩爸爸轮流做游戏。
爸爸每次可以取最左边或最右边的物品。
小孩每次选价值最大的{最左边,最右边}的物品,如果价值一样大, 则选取最左边的物品。
问你,爸爸想要输(价格严格小),而且差值尽可能少的最小差值是多少。
【分析】
首先,我们DP两个东西——
1, mx[l][r]表示对于区间[l, r],儿子先手,爸爸所能拿到的最大价值差值(差值是爸爸减儿子)
2, mn[l][r]表示对于区间[l, r],儿子先手,爸爸所能拿到的最小价值差值(差值是爸爸减儿子)
那么——
我们尝试使用搜索解决这个问题
dfs(l, r, dif)表示当前还没有取的区间范围是[l, r],儿子先手,此时爸爸减儿子的差值为dif。
那么——
1,这时先考虑剪枝——

设置初始ANS = -inf;

void dfs(int l, int r, int dif)
{
if (dif + mn[l][r] >= 0) return;//哪怕取一个最小值,都会赢了儿子,是个无效状态
if (dif + mx[l][r] <= ANS) return;//哪怕取一个最大值,差值都依然太小了,最优性剪枝
if (dif + mx[l][r] < 0)//取一个最大值,使得差值尽可能小,最优性剪枝
{
gmax(ANS, dif + mx[l][r]);
}
}

2,再模拟儿子的操作,获得新的(l, r, dif)

3,接着需要进一步地考虑爸爸的操作——
<1>取l,变成dfs(l + 1, r, dif + a[l]);
<2>取r,变成dfs(l, r - 1, dif + a[r]);

4,考虑如何获得DP数组mn[][]和mx[][]——

mn[i][i - 1] = mx[i][i - 1] = 0;

for(int l = n; l >= 1; --l)
{
for(int r = l; r <= n; ++r)
{
先模拟儿子的操作,获得新的(ll, rr)
然后考虑父亲的操作——
1,取ll:
gmax(mx[l][r], a[ll] + mx[ll + 1][rr]);
gmin(mn[l][r], a[ll] + mn[ll + 1][rr]);
2,取rr:
gmax(mx[l][r], a[rr] + mx[ll][rr - 1]);
gmin(mn[l][r], a[rr] + mn[ll][rr - 1]);

}
}


题目的意思是给出一个序列问能否去掉k的数之后使得整个序列不是递增也不是递减的
思路:先求出LIS,然后倒序求出最长递减子序列长度,然后判断去k的数后长度是否都大于所求长度

#include <bits/stdc++.h>
using namespace std;

int a[100005];
int b[100005];
int jl[100005];
int n,m,T;

int main()
{
for(scanf("%d",&T);T--;)
{
    scanf("%d %d",&n,&m);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
    }
    jl[0] = a[0];
    int len = 1;
    int tj;
    for(int i = 1; i < n; i++)
    {
        if(jl[len - 1] <= a[i])
        {
            jl[len++] = a[i];
        }
        else
        {
            tj = upper_bound(jl, jl + len, a[i]) - jl;
            jl[tj] = a[i];
        }
    }
    int al = len;
    for(int i = 0; i < n; i++)
        b[i] = a[n - i];
    jl[0] = b[0];
    len = 1;
    for(int i = 1; i < n; i++)
    {
        if(jl[len - 1] <= b[i])
        {
            jl[len++] = b[i];
        }
        else
        {
            tj = upper_bound(jl, jl + len, b[i]) - jl;
            jl[tj] = b[i];
        }
    }
    int bl = len;
    if(n - m > al && n - m > bl)
        printf("A is not a magic array.\n");
    else
        printf("A is a magic array.\n");
}
  return 0;
题目的意思是在斐波那契序列中找出k个数从凑出的数是good的,不能就是bad的,给出k,求最小的bad数
思路:找规律发现这个数是斐波那契第2*k+3项-1,数据较大矩阵快速幂搞定

6199

给定一个n个点m条边无向图(n,m<=1e5)
  支持两个操作
    1.添加一条边
    2.询问点u到点v的所有路径中必经边的条数
  操作数<=1e5
分析
  第一眼看起来像是要动态维护无向图的边双连通分量,看似无法做
  其实可以这样想,假设我们有一个树,那么这个树上的每一条边都是必经边
  现在假设多加一条非树边(u,v),那么相当于原树上u->v这条路径上的所有边都变成了非必经边
  也就是如果我们认为必经是1,非必经是0,那么所要做的两个操作就是1.将树上路径全部赋为0    2.询问树上路径的和
  这是裸的树链剖分,但可惜的是这题10组数据,卡了$O(nlog^2n)$的树链剖分
  这题就要用神奇的套路解决了
  注意到这里树上路径操作只是将1变成0,所以有效操作最多就n次,树链剖分中有很多没必要的赋值
  我们可以用并查集来解决,记录f[i]表示点i的上一个仍旧是1的点,这样对于修改直接利用并查集在树上跳,这样修改是$O(n)$的
  那么怎么处理询问呢
  我们可以维护每个点到根节点路径上权值和,那么询问的时候加一加减一减就行了,现在考虑如果将一条边u-fa[u]从1改成0,那么相当于以u为根的子树中所有的权值都要减去1
  所以这个我们可以先dfs序,然后相当于“改段求点”,直接树状数组就ok了


题意:
给你一棵树, 树上有点权, 要求选择起点S和终点T, 要求T-S-sum 最大, sum为S到T的边权。
思路:
根据题意就可以建图
建立源点和汇点。
源点连所有的树上点, 边权为 a[i], 所有树上点在连接 汇点, 边权为-a[i]. 然后在根据树建图。 
spfa跑个最长路即可。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
int T;

const int maxn = 100000 + 10;

int d[maxn];
int a[maxn];
struct Edge{
    int u, v, d;
    Edge(int u = 0,int v = 0,int d = 0):u(u), v(v), d(d){}
};
vector<Edge>edges;
int n;
vector<int>g[maxn];
void add(int u,int v,int w){
    edges.push_back(Edge(u, v, w));
    g[u].push_back((int)edges.size() - 1);
}
bool vis[maxn];
int cnt[maxn];
queue<int>q;
void spfa(){
    memset(vis,0,sizeof vis);
    memset(cnt,0,sizeof cnt);
    while(!q.empty())q.pop();
    vis[0] = 1; cnt[0] = 1;
    memset(d,0,sizeof d);
//    d[0] = 0;
    q.push(0);
    while(!q.empty()){
        int u = q.front(); q.pop();
        vis[u] = 0;
        for (int i = 0; i < g[u].size(); ++i){
            int id = g[u][i];
            int v = edges[id].v;
            int w = edges[id].d;
            if (d[v] < d[u] + w){
                d[v] = d[u] + w;
                if (!vis[v]){
                    vis[v] = 1;
                    cnt[v]++;
                    q.push(v);
                    if (cnt[v] > n) return;
                }
            }
        }
    }
    printf("%d\n", d[n+1]);
}
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        edges.clear();
        for (int i = 1; i <= n; ++i){
            scanf("%d", &a[i]);
            g[i].clear();
        }

        for (int i = 1; i <= n; ++i){
            add(0, i, a[i]);
            add(i, n+1, -a[i]);
        }
        for (int i = 1; i < n; ++i){
            int u, v, w;
            scanf("%d %d %d", &u, &v, &w);
            add(u, v, -w);
            add(v, u, -w);
        }
        spfa();
    }

    return 0;
}
题意就是求最少的点覆盖所有给定的q条路径,其实可以转换一下,就是求最多的路径集合使得集合内的路径两两之间不存在交点,那么跟HDU 4912是一样的题目了,先把所有路径按照其LCA深度从大到小排,即先处理靠下方的,因为这样对其他路径影响肯定是最小的,然后看这条路径的两端u和v是否被标记过,如果没标记就把以LCA(u,v)为根的子树全部标记上,否则就说明这条路径至少与其他路径存在一个交点(被标记的点)以此类推,如何快速标记子树呢?DFS序啊,而且子树是区间更新,查询端点是点查询,用BIT刚刚好
题意:求通过把最前面的一堆牌翻转到最后满足能把所有的牌的数目都取到的最少反转次数

思路:根据题意,牌的数量和消耗的费用是相同的,所以总有一种顺序能够取到所有的牌:
我们首先从顺序头开始扫描求和,出现和为负的情况时做一个标记,然后和重置为0,继续向后扫描,最后把所有和为负的长度相加就是需要反转的次数