Manthan, Codefest 17 B-F题目详解

被这套题搞的突然失去梦想。。
B题题意:给你三个值p,q,r,在一段序列上选取3个值ai,aj,ak(1<=i<=j<=k<=n)使得ai*p+aj *q+ak *r最大。
思路:做的时候想歪了,但弄懂了一个之前一直疑惑的问题。。之后认真思考这道题时发现不是很难想,首先博主做的时候认定为一道dp题。
dp转移方程为:dp[j][i] = max(dp[j - 1][i], dp[j][i - 1] + num[i] * a[j])
博主将p,q,r按顺序存进了num里面,那么dp[ j ][ i ]的意思为前j个数已经取了i个数的最大值。边界dp[j][1]=max(dp[j-1][i],num[1]*a[j]);
代码如下

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
ll a[100005];
ll num[10];
ll dp[100005][5];
const ll inf = 0x3f3f3f3f3f3f3f3f;
int main()
{

    long long n;
    cin >> n >> num[1] >> num[2] >> num[3];
    for (int i = 0;i <= n;i++)for (int j = 0;j <= 3;j++)dp[i][j] = -inf;
    for (int i = 1;i <= n;i++)
        scanf("%lld", &a[i]);
    dp[1][1] = num[1] * a[1];
    for (int i = 1;i <= 3;i++)
        for (int j = 1;j <= n;j++)
        {
            if (i == 1)
                dp[j][i] = max(dp[j - 1][i], num[1] * a[j]);
            else
                dp[j][i] = max(dp[j - 1][i], dp[j][i - 1] + num[i] * a[j]);
        }
    cout << dp[n][3] << endl;

}

可能有些大佬会用前缀后缀做这道题,但博主感觉那个方法不太实用,如果下次求4个数,就不好做了。。但这个dp却可以。
C题题意:有一棵树,结点的值有m种,有一个值比较特殊,记为k,如果有一个结点选了它,那么他周围的点的值不能超过k。特殊的值有x个,问有多少种方法赋值。
思路:一眼望去就是一道树形dp题,然而加上特殊的值有x个这个限制条件就不好做了,虽然转移方程写得出来。
转移方程如下:
dp[cur][0][cnt]代表当前结点cur的值小于k,它的子树有cnt个特殊的点的答案。
dp[cur][1][cnt]代表当前结点cur的值等于k,它的子树(包括自己)有cnt个特殊的点的答案。
dp[cur][2][cnt]代表当前结点cur的值大于k,它的子树有cnt个特殊的点的答案。
聪明的读者一定能够将状态转移推出来,但如何求在子树上分配特殊点的数量,这是个问题。这其中又有一个递推关系在里面。如果有x个特殊点,有size个儿子。那么求分配特殊点的数量这个问题又可以变成若干个子问题:最后一个儿子有1个,前size-1个儿子有x-1个.如果最后一个儿子有2个,那么前size-1个孩子就要x-2个。。。
所以到一个儿子的树时,只要知道前一个儿子的各种分配方式的数量,那么也就知道现在的分配数量是多少了。
细节请看代码:

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int maxn = 1e5 + 5;
vector<int>V[maxn];
typedef long long ll;
int k, x;
int n, m;
ll dp[maxn][3][15];
ll a[3][15], b[3][15];
const ll mod = 1000000007;
void dfs(int u, int pre)
{
    int Size = V[u].size();
    if (Size == 1 && V[u][0] == pre)
    {
        dp[u][0][0] = k - 1;
        dp[u][1][1] = 1;
        dp[u][2][0] = m - k;
        return;
    }
    for (int i = 0;i < Size;i++)
    {
        int to = V[u][i];
        if (to == pre)continue;
        dfs(to, u);
    }

    memset(a, 0, sizeof(a));
    memset(b, 0, sizeof(b));
    for (int i = 0;i < 3;i++)a[i][0] = 1;
    //a[1][1] = 1;
    for (int to : V[u])
    {
        if (to == pre)continue;
        for(int c=0;c<3;c++)
            for(int i=0;i<=x;i++)
                for (int j = 0;j <= x;j++)
                {
                    if (i + j > x)break;
                    ll temp = 0;
                    if (c == 0)
                    {
                        temp = dp[to][0][j] + dp[to][1][j];
                        temp %= mod;
                        temp += dp[to][2][j];
                        temp %= mod;
                        b[c][i + j] += temp*a[c][i] % mod;
                        b[c][i + j] %= mod;
                    }
                    else if (c == 1)
                    {
                        temp = dp[to][0][j] % mod;
                        b[c][i + j] += temp*a[c][i] % mod;
                        b[c][i + j] %= mod;
                    }
                    else
                    {
                        temp = dp[to][0][j] + dp[to][2][j];
                        temp %= mod;
                        b[c][i + j] += temp*a[c][i] % mod;
                        b[c][i + j] %= mod;
                    }
                }
        for (int i = 0;i < 3;i++)
            for (int j = 0;j <= x;j++)
                a[i][j] = b[i][j], b[i][j] = 0;
    } 
    for (int l = 0;l <= x;l++)
    {
        dp[u][0][l] = (1ll * a[0][l] * (k - 1)) % mod;
        if (l >= 1)
            dp[u][1][l] = a[1][l - 1];
        dp[u][2][l] = (1ll * a[2][l] * (m - k)) % mod;
    }
}


int main()
{

    cin >> n >> m;
    int u, v;
    for (int i = 1;i < n;i++)
    {
        scanf("%d%d", &u, &v);
        V[u].push_back(v);
        V[v].push_back(u);
    }

    cin >> k >> x;
    dfs(1, 0);
    ll ans = 0;
    for (int i = 0;i < 3;i++)
        for (int j = 0;j <= x;j++)
        {
            ans += dp[1][i][j];
            ans %= mod;
        }
    cout << ans << endl;
}

D题题意:(好复杂的题意啊。。)又是一棵树,,父与子的关系有2种,一种是儿子是父亲的special case,另一种儿子是父亲的part。并且,如果v是u的special case或者part,u是t的special case或者part,那么v也是t的special case或者part。如果v是u的part,而t是u的special case,那么v也是t的part。(虽然有点绕,但题目大概意思应该讲通了。。)
现在有若干个询问1 u v代表v是不是u的special case。2 u v 代表v是不是u的part
思路:读者请捋顺其中的关系。。如果是询问1,那么其实就是再问你u是不是v的祖先,并且u到v的边都是第一种边。询问2其实问你u和v共同祖先k到v的边是不是都是第二种,k到u的边是不是都是第一种。
所以这道题只要看清题意,注意细节,会求lca就做出来了。
代码如下:

#include<iostream>
#include<vector>
using namespace std;
const int maxn = 1e5 + 5;
vector<int>V1[maxn], V2[maxn];
int dep[maxn];
int One[maxn], Two[maxn];
int st[maxn][20];
int f[maxn];
int n;
int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); }
void dfs(int u, int d,int one,int two)
{
    int Size = V1[u].size();
    dep[u] = d;
    One[u] = one, Two[u] = two;
    int x = find(u);
    for (int i = 0;i < Size;i++)
    {
        int y = find(V1[u][i]);
        if (x != y)
            f[y] = x;
        st[V1[u][i]][0] = u;
        if (V2[u][i] == 0)
            dfs(V1[u][i], d + 1, one + 1, two);
        else
            dfs(V1[u][i], d + 1, one, two + 1);
    }
}

void ST()
{
    for(int i=1;i<20;i++)
        for (int j = 1;j <= n;j++)
        {
            if ((1 << i) > dep[j])continue;
            st[j][i] = st[st[j][i - 1]][i - 1];
        }
}
int query(int u, int v)
{
    if (dep[u] < dep[v])swap(u, v);
    int d = dep[u] - dep[v];
    for (int i = 0;i < 20;i++)
        if (d&(1 << i))
            u = st[u][i];
    if (u == v)return u;

    for (int i = 19;i >= 0;i--)
    {
        if (st[u][i] != st[v][i])
        {
            u = st[u][i];
            v = st[v][i];
        }

    }
    u = st[u][0];
    v = st[u][0];
    return u;
}
int main()
{

    cin >> n;
    int parent, type;
    for (int i = 1;i <= n;i++)
    {
        f[i] = i;
        scanf("%d%d", &parent, &type);
        if (parent == -1 && type == -1)continue;
        V1[parent].push_back(i);
        V2[parent].push_back(type);
    }
    for (int i = 1;i <= n;i++)
        if (dep[i] == 0)
        {
            dfs(i, 1,0, 0);
        }
    ST();

    int q;
    cin >> q;
    int u, v;
    for (int i = 1;i <= q;i++)
    {
        scanf("%d%d%d", &type, &u, &v);
        int x = find(u);
        int y = find(v);
        if (x != y)
            puts("NO");
        else
        {
            if (type == 1)
            {
                if (query(u, v) != u || u == v||Two[v]-Two[u]!=0)
                    puts("NO");
                else
                    puts("YES");
            }
            else
            {
                int thefa = query(u, v);
                if (Two[u] - Two[thefa] != 0 || One[v] - One[thefa] != 0||thefa==v)
                    puts("NO");
                else
                    puts("YES");
            }
        }
    }
    return 0;
}

请一定要自己写一遍,注意if里面的语句。这些细节可能导致你会wa掉。。
E题题意:将一个数转换为b进制后,如果0到b-1都出现了偶数次(包括0次,不包括前导0),这个数就是magic数。问l到r在b进制下中有几个magic数。
思路:典型的数位dp,只要想办法来判断这个状态合不合法这道题就可以a了。
我们可以用dp[b][pos][now]来代表在b进制下最高位到pos位下的状态now的magic数的个数。
那么now状态是什么呢?如何用now来判断该数的合法性呢?
如果前面有一位数为c,那么就用now^(1<< c),那么这样就可以利用now是否为0来判断是不是合法了。
代码如下:

#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
ll dp[12][100][5000];
ll l, r;
int b;
int a[105];
ll dfs(int pos, ll now, bool lead, bool limit)
{
    if (pos == -1)
    {
        if (now == 0)return 1;
        else return 0;
    }
    if (!limit && !lead&&dp[b][pos][now] != -1)
        return dp[b][pos][now];
    int up = limit ? a[pos] : b - 1;
    ll ans = 0;
    for (int i = 0;i <= up;i++)
    {
        ans += dfs(pos - 1, lead&&i==0?now:now ^ (1LL << i), lead&&i == 0, limit&&i == a[pos]);
    }
    if (!limit && !lead)dp[b][pos][now] = ans;
    return ans;

}

ll solve(ll x)
{
    int pos = 0;
    while (x)
    {
        a[pos++] = x%b;
        x /= b;
    }
    return dfs(pos - 1, 0, true, true);
}

int main()
{
    int T;
    scanf("%d", &T);
    memset(dp, -1, sizeof(dp));
    while (T--)
    {
        scanf("%d%lld%lld", &b, &l, &r);
        /*for (int i = 0;i <= r;i++)
            cout << solve(i) << endl;
        solve(r);
        solve(l - 1);*/
        printf("%lld\n", solve(r) - solve(l - 1));
    }
    return 0;
}

F题题意:在笛卡尔坐标中,有q次询问,第一种询问1 l r k,代表在x>=l&&x

#include<iostream>
#pragma GCC optimize("O3")
#pragma GCC target("sse4")
using namespace std;
typedef long long ll;
int a[100005], b[100005], c[100005];
int main()
{
    int q;
    cin >> q;
    int type,l,r;
    int  k;
    while (q--)
    {
        scanf("%d%d%d", &type, &l, &r);
        if (type == 1)
        {
            scanf("%d", &k);
            if (k > 0)
            {
                for (int i = l;i < r;i++)
                {
                    if (!a[i] || a[i] && a[i] > k)
                        a[i] = k;
                    if (b[i])
                        c[i] = a[i] + b[i];
                }
            }
            else
            {
                k = -k;
                for (int i = l;i < r;i++)
                {
                    if (!b[i] || b[i] && b[i] > k)
                        b[i] = k;
                    if (a[i])
                        c[i] = a[i] + b[i];
                }
            }
        }
        else
        {
            ll ans = 0;
            for (int i = l;i < r;i++)
                ans =ans+ c[i];
            printf("%lld\n", ans);
        }
    }
    return 0;
}

结果竟然出人意料的A了。。
所以呢,放这道题的意思就是希望读者不要看到数据量那么大,就不敢做了。尝试一下暴力说不定就过了呢。。
ok,正解肯定不是这个,博主看了看题解,正解是分块。但博主还没弄懂,有兴趣的读者可以去看看Tutorial,稍后可能会更新正解。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值