2015多校联赛第三场(部分题解)

1001. Magician (hdu5316)

这个题目用到线段树来合并区间,题目有句话的意思A beautiful subsequence is a subsequence that all the adjacent pairs of elves in the sequence have a different parity of position说的是相邻的两个元素必须有不同的奇偶位置,所有一个序列有四种可能的形式,奇...奇, 奇...偶,偶...奇, 偶...偶,这四种形式只是端点的奇偶性,所以线段树的每个节点就得有四个值,分别维护这四个值就行了,举个例子来说,根节点的偶偶的最大值取决于 左孩子的偶偶 + 右孩子的奇偶, 左孩子的偶奇 + 右孩子的偶偶, 左孩子的偶偶,右孩子的偶偶, 所以根节点的偶偶就等于他们当中的最大值,同理,其他的三个也是这样,剩下的另外一种操作就是更新点了。

#include<iostream>
#include <stdio.h>
using namespace std;
typedef long long LL;
const int maxn = 100010;
const LL inf = 1LL << 61;
LL d[maxn];
struct Data{
    LL s00, s01, s10, s11;//s00表示偶偶, s01表示偶奇, s10表示奇偶,s11表示奇奇
    void init()
    {
        s00 = s01 = s10 = s11 = -inf;//初始化一定要为负无穷,因为可能有负数
    }
    LL max_ele()
    {
        return max(max(s00, s01), max(s10, s11));//找出他们当中最大的
    }
};
struct tree{
    Data data;
};
tree sum[maxn * 4];//线段树
void checkmax(LL &a, const LL b)
{
    if (a < b)
        a = b;
}
Data operator + (const Data &a, const Data &b)
{
    Data ret;
    ret.s11 = max(a.s11 + b.s01, a.s10 + b.s11); checkmax(ret.s11, a.s11); checkmax(ret.s11, b.s11);
    ret.s10 = max(a.s11 + b.s00, a.s10 + b.s10); checkmax(ret.s10, a.s10); checkmax(ret.s10, b.s10);
    ret.s00 = max(a.s00 + b.s10, a.s01 + b.s00); checkmax(ret.s00, a.s00); checkmax(ret.s00, b.s00);
    ret.s01 = max(a.s00 + b.s11, a.s01 + b.s01); checkmax(ret.s01, a.s01); checkmax(ret.s01, b.s01);
    return ret;
}
void pushup(int rt)
{
    sum[rt].data = sum[rt<<1].data + sum[rt<<1|1].data;
}
void build(int L, int R, int rt)
{
    sum[rt].data.init();
    if (L == R)
    {
        if (L & 1)
            sum[rt].data.s11 = d[L];
        else
            sum[rt].data.s00 = d[L];
        return;
    }
    int mid = (L + R) / 2;
    build(L, mid, rt<<1);
    build(mid + 1, R, rt<<1|1);
    pushup(rt);
}
void update(int pos, LL value, int L, int R, int rt)
{
    if (L == pos && R == pos)
    {    
        if (pos & 1)
            sum[rt].data.s11 = value;
        else
            sum[rt].data.s00 = value;
        return;
    }
    int mid = (L + R) / 2;
    if (pos <= mid)
        update(pos, value, L, mid, rt<<1);
    else
        update(pos, value, mid + 1, R, rt<<1|1);
    pushup(rt);
}
Data query(int l, int r, int L, int R, int rt)
{
    if (l <= L && r >= R)
    {
        return sum[rt].data;
    }
    int mid = (L + R) >> 1;
    Data lson, rson;
    lson.init(); rson.init();
    if (l <= mid)
        lson = query(l, r, L, mid, rt<<1);
    if (r > mid)
        rson = query(l, r, mid + 1, R, rt<<1|1);
    return lson + rson;
}
int main()
{
    int T, n, m;
    scanf("%d", &T);
    while (T--)
    {

        scanf("%d %d", &n ,&m);
        for (int i = 1; i <= n; i++)
            scanf("%I64d", &d[i]);
        build(1, n, 1);
        int op, a, b;
        for (int i = 0; i < m; i++)
        {
            scanf("%d %d %d", &op, &a, &b);
            if (op)
                update(a, (LL)b, 1, n, 1);
            else
            {
                Data ans = query(a, b, 1, n, 1);
                printf("%I64d\n", ans.max_ele());
            }
        }
    }
    return 0;
}
View Code

1002.RGCDQ (hdu5317)

这道题F(x)表示x当中素因子的个数,由于x最大1000000,所以F(x)最多不超过8个,这个可以拿出前几个素数乘一下试试,2*3*5*7*11*13*17*19=9699690>1000000所以可以先预处理出来每个数的F(x),然后求出他的前缀和,用O(1)的复杂度去查询,求F(x)的时候注意不要除着求,要直接像筛素数一样去求。具体见代码

#include <cstdio>
#include <string.h>
const int maxn = 1000010;
int prime[maxn];//标记筛选的素数
int f[maxn];//F(x)
int S[maxn][9];//前缀和,因为一共最多可能有8个,所以开个8的数组足够了,0的位置为空
int pr[100000];//保存素数
void init()
{
    memset(prime, true, sizeof(prime));
    prime[1] = prime[0] = false;
    for (int i = 2; i * i <= 1000000; i++)//素数筛选
    {
        if (prime[i])
            for (int j = i + i; j <= 1000000; j += i)
                prime[j] = false;
    }
    int tot = 0;
    for (int i = 2; i <= 1000000; i++)
        if (prime[i])
            pr[tot++] = i;//将筛选出来的素数放到pr中
    for (int i = 0; i < tot; i++)//求F(x),这里很关键,不能放到素数筛选的过程当中去,因为有大素数
    {
        for (int j = pr[i]; j <= 1000000; j += pr[i])//所有包含pr[i]这个素因子的都要加1
            f[j]++;
    }
    for (int i = 2; i <= 1000000; i++)
    {
        for (int j = 1; j <= 8; j++)
        {
            S[i][j] = (j == f[i]) ? S[i - 1][j] + 1 : S[i - 1][j];//求出前缀和
        }
    }
}

int main()
{
    init();
    int T, L, R;
    scanf("%d",  &T);
    while (T--)
    {
        scanf("%d %d", &L, &R);
        int ans = 1;
        for (int i = 8; i > 0; i--)
        {
            
            if (S[R][i] - S[L - 1][i] > 1)//找出这个区间最大的来
            {
                ans = i;
                break;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}
View Code

1003.The Goddess Of The Moon (hdu5318)

给出n条链子,让你选择m个,问一共方案有多少种。因为m比较大(1e9),所以用矩阵快速幂来解。构造一个矩阵表示任意两个之间是否可以链接,如果可以链接的话就是1,不能链接的话就是0,这个题有点模糊。大致知道一点意思,但是细推的话,状态转移方程不太清楚。。。看题解上写S[i][j]表示前i个,以j结尾的方案数,估计意思就是当链接i个的时候,最后以j结尾的方案数,他就应该等于前i - 1个所有能链接j,并且j链接在后面的个数,对于每一个j,对应一列,所以,就是一个矩阵,而S[i][j]可以由S[i - 1][]来推,那么就构成了矩阵连乘的条件,所以就能用矩阵快速幂了。这个题还是不是完全透彻的理解。如果哪位大牛路过,恳请指点。

#include<iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long LL;
const int maxn = 52;
const int mod = 1000000007;
int sz;
struct Mat{
    int mat[maxn][maxn];
    void init()
    {
        memset(mat, 0, sizeof(mat));
    }
};
Mat operator * (const Mat &a, const Mat &b)//矩阵乘法
{
    Mat ret;
    ret.init();
    for (int i = 0; i < sz; i++)
    {
        for (int j = 0; j < sz; j++)
        {
            ret.mat[i][j] = 0;
            for (int k = 0; k < sz; k++)
            {
                ret.mat[i][j] += 1LL * a.mat[i][k] * b.mat[k][j] % mod;
                ret.mat[i][j] %= mod;
            }

        }
    }
    return ret;
}
Mat operator ^ (Mat a, int b)//矩阵快速幂
{
    Mat ans;
    for (int i = 0; i < sz; i++)
        for (int j = 0; j < sz; j++)
            ans.mat[i][j] = (i == j ? 1 : 0);
    while (b)
    {
        if (b & 1)
            ans = ans * a;
        a = a * a;
        b >>= 1;
    }
    return ans;
} 
bool connect(int a, int b)//判断a链和b链是否能连接
{
    int dig1[maxn], dig2[maxn];
    int tot1 = 0, tot2 = 0;
    for (; a; a /= 10)
        dig1[tot1++] = a % 10;
    for (; b; b /= 10)
        dig2[tot2++] = b % 10;
    int ans = 1;
    for (int len = 1; len <= min(tot1, tot2); len++)
    {
        bool flag = false;
        for (int i = len - 1; i >= 0; i--)
        {
            if (dig1[i] != dig2[tot2 - (len - i)])
            {
                flag = true;
                break;
            }
        }
        if (!flag)
            ans = len;
    }
    if (ans > 1)
        return true;
    return false;
}
int solve(vector <int> num, int n)
{
    vector<int> tmp = num;
    sort(tmp.begin(), tmp.end());
    tmp.erase(unique(tmp.begin(), tmp.end()), tmp.end());//去重
    int m = (int)tmp.size();
    Mat A, B;
    sz = m;
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < m; j++)
            B.mat[i][j] = connect(tmp[i], tmp[j]);//构造转移矩阵
    }
    A.init();
    for (int i = 0; i < m; i++)
        A.mat[0][i] = 1; //初始第一行全为1
    B = B ^ (n - 1);
    A = A * B;
    int ans = 0;
    for (int i = 0; i < m; i++)
    {
        ans += A.mat[0][i];
        ans %= mod;
    }
    return ans;
}
int main()
{
    int T, n, m;
    scanf("%d", &T);
    while (T--)    
    {
        scanf("%d%d", &n, &m);
        vector<int> v(n);
        int tmp;
        for (int i = 0; i < n; i++)
        {
            scanf("%d", &v[i]);
        }
        printf("%d\n", solve(v, m));
    }
    return 0;
}
View Code

1004.Painter (hdu5319)

这个题属于模拟题,红色只能正向斜着刷(\),蓝色只能反向斜着刷(/),被正向反向刷了两次的点是绿色,给出最终状态求出最少经过多少步能到这种状态。直接枚举每一个点就行。

#include <cstdio>
#include <cstring>
const int maxn = 60;
char a[maxn][maxn];
int mp[maxn][maxn];
int m;
int ans;
int n;
const int dir[2][2] = {1, 1, 1, -1};
void work(int x, int y, int color)
{
    mp[x][y] -= color;
    while (true)
    {
        x += dir[color - 1][0];
        y += dir[color - 1][1];
        if (x >= 0 && y >= 0 && x < n && y < m && (mp[x][y] == color || mp[x][y] == 3)) 
            mp[x][y] -= color;
        else
            break;
    }
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d", &n);
        for (int i = 0; i < n; i++)
        {
            scanf("%s", a[i]);
            m = strlen(a[i]);
                for (int j = 0; j < m; j++)
                {
                    if (a[i][j] == 'R')//转化成数字便于计算
                        mp[i][j] = 1;
                    else if (a[i][j] == 'B')
                        mp[i][j] = 2;
                    else if (a[i][j] == 'G')
                        mp[i][j] = 3;
                    else
                        mp[i][j] = 0;
                }
        }
        ans = 0;
        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < m; j++)
            {
                if (mp[i][j] == 0) continue;
                if (mp[i][j] == 1)
                    work(i, j, 1);
                else if (mp[i][j] == 2)
                    work(i, j, 2);
                else
                {
                    work(i, j, 1);
                    work(i, j, 2);
                    ans++;
                }
                ans++;
            }
        }
        printf("%d\n", ans);
        
    }
    return 0;
}
View Code

1008.Solve this interesting problem (hdu5323)

此题给出线段树的区间定义,给出一个节点的左区间端点和右区间端点,求最小n,其中n为根节点的右端点。

从当前节点(L, R)往他的父节点走的话有四种路线,分别是:当他为左孩子的时候父节点的区间端点(L, R + (R - L + 1))或者(L, R + (R - L + 1) - 1),当他为右孩子的时候父节点的区间为(L - (R - L + 1), R)或者(L - (R - L + 1) - 1, R),因为线段树节点的左右孩子区间长度要不 相等,要不 左孩子比右孩子大1, 所以根据这个结论所以就可以得出上面四种方式,其中(R - L + 1)为区间长度,题目中还给了一个关键的就是L / (R - L + 1) <= 2015,这个就说明 L <= 2015 * len, 其中len为区间长度,线段树中len越小,也就是左右端点靠的越近,他的位置越深,就比如叶子节点的len为0,所以他在最下面,这就说明了一个问题就是当这个树取最深的顶点的时候,那么len取1,所以L<=2015, 并且从2015开始往上找到0的时候最多22层,当是左孩子的时候11层,右孩子11层。所以搜索这四种路径。

#include<iostream>
#include <cstdio>

using namespace std;
typedef long long LL;
const LL inf = 1LL << 60;
LL n;
void search(LL L, LL R)
{
    if (L < 0 || L > R)
        return;
    if (L == 0)
    {
        n = min(n, R);
        return ;
    }
    int len = R - L + 1;
    if (L < len)//剪枝,因为如果l小于区间长度的话,那么他的父节点的左端点一定为负值,所以不符合
        return;
    search(L, R + len);//左孩子
    if (len != 1)
        search(L, R + len - 1);//左孩子
    search(L - len, R);//右孩子
    search(L - len - 1, R);//右孩子
}
LL work(int L, int R)
{
    n = inf;
    search(L, R);
    return n == inf ? -1 : n;
}
int main()
{

    int L, R;
    while (~scanf("%d %d", &L, &R))
    {
        printf("%lld\n", work(L, R));
    }
    return 0;
}
View Code

1011. Work (hdu5326)

官方签到题。直接dfs即可

#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int maxn = 110;
vector<int> mp[maxn];
int in[maxn];
int n, k;
int siz[maxn];
int dfs(int u)
{
    siz[u] = 1;
    int len = mp[u].size();
    for (int i = 0; i < len; i++)
    {
        siz[u] += dfs(mp[u][i]);
    }
    return siz[u];
}
int main()
{
    while (~scanf("%d%d", &n, &k))
    {
        memset(siz, 0, sizeof(siz));
        memset(in, 0, sizeof(in));
        for (int i = 0; i <= n; i++)
            mp[i].clear();
        int a, b;
        for (int i = 1; i < n; i++)
        {
            scanf("%d%d", &a, &b);
            mp[a].push_back(b);
            in[b]++;
        }
        int root = 0;
        for (int i = 1; i <= n; i++)
            if (!in[i])
                root = i;
        dfs(root);
        int ans = 0;
        for (int i = 1; i <= n; i++)
            if (siz[i] - 1 == k)
                ans++;
        printf("%d\n", ans);
    }
    return 0;
}
View Code

其它题目未补完。。。


 

posted @ 2015-07-30 10:19  Howe_Young  阅读(265)  评论(0编辑  收藏  举报