test 11-10 [水题 状压DP dfs序+线段树]

3 篇文章 0 订阅
2 篇文章 0 订阅

第一题水题,略过。。。

T2

【题目描述】

我们要从n种食物选m个出来,安排一个顺序吃掉它(们),每种食物有个美味值ai,然后我们有k个规则,每个规则有 xi, yi 和 ci三个数,如果吃完第xi种食物接下来马上吃第yi种食物,第j种食物的美味值会增加ci。每种食物至多吃一个,求美味值最大的和是多少?

【输入格式】

第一行有三个数n,m,k,k代表有k个规则(0<=k<=n*(n-1))。
第二行有n个数字代表每个食物的美味值。
接下去有k行,每行三个数xi,yi,ci。保证没有任意两个规则的xi和yi同时相同。

【输出格式】

一行一个数代表答案

【sample input1】

2 2 1
1 1
2 1 1

【sample output1】

3

【sample input 2】

4 3 2
1 2 3 4
2 1 5
3 4 2

【sample output 2】

12

【数据范围】

30% m<=n<=5 ,0<=ci,ai<=1e5
100% m<=n<=18,0<=ci,ai<=1e9

状压dp,用记忆化搜索比较简便,主要是再加一个状态,表示上一次取的,即可得转移方程:
dp(S,i,j)=max{dp(S’,k,j-1)}
其中,j,这个状态是指取了多少个,但是S已经表示了,记忆化的时候不用加这一维,这里只是为了方便。

代码样本:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 25
#define maxbit 300000
typedef long long ll;
ll f[maxbit][maxn],w[maxn],ad[maxn][maxn];
int p[maxn],n,m,k;
bool vis[maxbit][maxn];
ll dp(int s,int las,int nu)
{
    if(nu==m)return 0;
    if(vis[s][las])return f[s][las];
    ll &ans=f[s][las];
    for(int i=1;i<=n;i++)
    {
        if(s&p[i])continue;
        if(ad[las][i])
            ans=(ll)max(ans,dp(s|p[i],i,nu+1)+ad[las][i]+w[i]);
        else
            ans=(ll)max(ans,dp(s|p[i],i,nu+1)+w[i]);
    }
    vis[s][las]=true;
    return ans;
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
    {
        scanf("%I64d",&w[i]);
        p[i]=1<<(i-1);
    }
    int a,b;
    ll val;
    while(k--)
    {
        scanf("%d%d%I64d",&a,&b,&val);
        ad[a][b]=val;
    }
    printf("%I64d",dp(0,0,0));
    return 0;
}

T3

【题目描述】

    历史上有一个著名的王国。它的所有城市互相连通并且构成一棵树。城市1为首都也就是这棵树的根。 
    因为外来的入侵,国王决定在某些城市加派士兵。所有城市初始士兵数量为0。当城市i 被加派了k 名士兵时。城市i 的所有子城市需要被加派k+1 名士兵。这些子城市的所有子城市需要被加派k+2 名士兵。以此类推。
    当然,加派士兵的同时,国王也需要不断了解当前的情况。于是他随时可能询问以城市i 为根的子树中的所有城市共被加派了多少士兵。 
    你现在是国王的军事大臣,你能回答出国王的每个询问么? 

【输入】

第一行,包含两个整数N,P 代表城市数量以及国王的命令的数量。 
接下来的P 行,每行代表国王的一个命令,命令分两种 
A X K :在城市X 加入K 个士兵 
Q X :询问以城市X 为根的子树中所有士兵数量的和 

【输出】

对于每个Q,输出答案。 

【输入样例】

7 10 
1 1 2 2 5 5 
Q 1 
A 2 1 
Q 1 
Q 2 
Q 5 
A 5 0 
Q 5 
A 3 1 
Q 1 
Q 2 

【输出样例】

0 
11 
11 
8 
10 
14 
13 

【数据范围】

对于50%的数据, 1<=N<=1000 1<=P<=300 
对于100%的数据, 1<=N<=50000 1<=P<=100000 1<=X<=N 0<=K<=1000

经过推导,可得: ans[u]=kd[u]numson[u]+Sigma(d[son[u]])
然后把点用dfs序存下
最后线段树,区间修改,区间查找
即:最终的值等于k减去该点深度,乘以子树大小加上子树深度和。
记住要记录add和tim(加的次数)的值,而且add可能为0,因此判断pushdown的标准是tim不为0而不是add

代码样本:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<set>
#include<algorithm>
using namespace std;
#define L(u) (u<<1)
#define R(u) ((u<<1)|1)
#define maxn 50005
struct tre{
    int l,r;
}nod[maxn*4];
typedef long long ll;
ll d[maxn],ma[maxn],sum[maxn*4],ad[maxn*4],sd[maxn],ti[maxn*4],k;
int head[maxn],tov[maxn],next[maxn],n,p,a,size[maxn],dfn[maxn],num;
int dfs(int u)
{
    size[u]=1;
    dfn[u]=++num;
    ma[num]=u;
    int v=head[u];
    while(v)
    {
        d[tov[v]]=d[u]+1;
        size[u]+=dfs(tov[v]);
        v=next[v];
    }
    return size[u];
}
void build(int u,int le,int ri)
{
    nod[u].l=le,nod[u].r=ri;
    if(le==ri)
    {
        return ;
    }
    int mid=(le+ri)/2;
    build(L(u),le,mid);
    build(R(u),mid+1,ri);
}
ll upg(ll val,int po)
{
    return val-d[po];
}
void pushdown(int u)
{
    sum[L(u)]+=(nod[L(u)].r-nod[L(u)].l+1)*ad[u]+(sd[nod[L(u)].r]-sd[nod[L(u)].l-1])*ti[u];
    sum[R(u)]+=(nod[R(u)].r-nod[R(u)].l+1)*ad[u]+(sd[nod[R(u)].r]-sd[nod[R(u)].l-1])*ti[u];
    ad[L(u)]+=ad[u];
    ad[R(u)]+=ad[u];
    ti[L(u)]+=ti[u];
    ti[R(u)]+=ti[u];
    ad[u]=0;
    ti[u]=0;
}
void update(int u,int le,int ri,ll val,int po)
{
    if(nod[u].l>=le&&nod[u].r<=ri)
    {
        sum[u]+=(nod[u].r-nod[u].l+1)*upg(val,po)+sd[nod[u].r]-sd[nod[u].l-1];
        ad[u]+=upg(val,po);
        ti[u]++;
        return;
    }
    if(ad[u]||ti[u]) pushdown(u);
    int mid=(nod[u].l+nod[u].r)/2;
    if(ri<=mid)update(L(u),le,ri,val,po);
    else if(le>mid)update(R(u),le,ri,val,po);
    else
    {
        update(L(u),le,mid,val,po);
        update(R(u),mid+1,ri,val,po);
    }
    sum[u]=sum[L(u)]+sum[R(u)];
}
ll query(int u,int le,int ri)
{
    if(nod[u].l==le&&nod[u].r==ri)
    {
        return sum[u];
    }
    if(ad[u]||ti[u])pushdown(u);
    int mid=(nod[u].l+nod[u].r)/2;
    if(ri<=mid)return query(L(u),le,ri);
    else if(le>mid) return query(R(u),le,ri);
    else
        return query(L(u),le,mid)+query(R(u),mid+1,ri);
}
int main()
{
    freopen("c.in","r",stdin);
    freopen("c.out","w",stdout);
    scanf("%d%d",&n,&p);
    for(int i=2;i<=n;i++)
    {
        scanf("%d",&a);
        tov[i]=i;
        next[i]=head[a];
        head[a]=i;
    }
    d[1]=1;
    dfs(1);
    for(int i=1;i<=n;i++)
        sd[i]=sd[i-1]+d[ma[i]];
    build(1,1,n);
    char op;
    while(p--)
    {
        scanf(" %c ",&op);
        if(op=='A')
        {
            scanf("%d%I64d",&a,&k);
            update(1,dfn[a],dfn[a]+size[a]-1,k,a);
        }
        else
        {
            scanf("%d",&a);
            printf("%I64d\n",query(1,dfn[a],dfn[a]+size[a]-1));
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值