2017.10.12队内互测——新一波高能胡策题

出题人:Chairman ,LXT ,qrc ,wyh

Problem 1:

题目来源:http://codevs.cn/problem/2169/
https://www.luogu.org/problem/show?pid=2376
https://www.luogu.org/problem/show?pid=2619
http://www.tyvj.cn/p/1032
这里写图片描述
考场上没有想到直接用除法计算次数…而是二分了…而且不是用最小面额的硬币使当前总和超过需求值…而是用了下一小的面额的硬币….

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int n,c,ls,x;
int bs[100010]; 
long long ans;
struct cons
{
    int v,b;
}p[100010];
bool flag;
bool cmp(cons a,cons b)
{
    return a.v>b.v;
}
int check(int k,int s)
{
    if((p[k].v*s)>x)
    return 0;
    else return 1;
}
int find(int k)
{
    int l=1;//存在次数为1的情况,边界不可以设为0
    int r=p[k].b;
    while(l+1<r)
    {
        int mid=(l+r)>>1;
        if(check(k,mid))
        l=mid;
        else r=mid;
    }
    return l;
}
int main()
{
    scanf("%d%d",&n,&c);
    for(int i=1;i<=n;i++)
    scanf("%d%d",&p[i].v,&p[i].b);
    sort(p+1,p+n+1,cmp);//按面额从大到下排序
    for(int i=n;i>=1;i--)
    {
        if(p[i].v>=c)//面额大于需求值的不用考虑,直接加进答案即可
        {
            ls=i+1;
            break;
        }
    }
    while(!flag)
    {
        x=c;
        for(int i=ls;i<=n;++i)
        if(p[i].v<=x&&p[i].b>0)//先用较大面额的硬币,尽量接近需求值 
        {
            int t=min(x/p[i].v,p[i].b);//int t=min(find(i),p[i].b);
            x-=t*p[i].v;
            p[i].b-=t;
        }
        if(x>0)//用较小面额的硬币,满足>=c的条件,尽量减少浪费 
        {
            for(int i=n;i>=ls;i--)
            if(x<=p[i].v&&p[i].b>0)
            {
                p[i].b--;
                x=0;
                break;
            }
        }
        if(x>0) break;
        ans++;
    }
    for(int i=1;i<ls;i++)
    ans+=p[i].b;
    printf("%lld\n",ans);
    return 0;
}

Problem 2:

题目来源:http://codevs.cn/problem/2451/
https://www.luogu.org/problem/show?pid=1896
http://www.lydsy.com/JudgeOnline/problem.php?id=1087
这里写图片描述
状压DP
用01串表示某一行放置的情况,直接枚举每一种状态需要2^9,考虑DP
dp[i][]j[h]表示第i行,从开始到现在已经放了j个国王,且第i行的状态(编号)是h的方案数。
则dp[i][j][h]=Σ(dp[i-1][j-cnt[h]][k]),k表示上一行的状态(编号),cnt表示h状态已经放置了的国王的数量。stay[i]储存i号状态的状态值,mp[i][j]表示i状态与j状态能否相临。
找出合法状态,删去不合法状态,预处理每一行的所有可行状态,枚举任意两种状态可否作为相邻两行进行摆放。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define RI register int
using namespace std;
typedef long long ll;
int n,ks,tot;
ll ans,pos;
ll dp[10][110][1010],stay[1010],cnt[1010];
bool mp[1010][1010];
void dfs(int k,int put,ll pos)//预处理出所有状态 
{
    stay[++tot]=pos;//存状态 
    cnt[tot]=k;//存放置棋子个数 
    if(k>=(n+1)/2||k>=ks)//边界条件 
    return;
    for(RI i=put+2;i<=n;i++)//不可以相临放置故+2
    dfs(k+1,i,pos+(1<<(i-1)));//状态改变
}
void init()
{
    dfs(0,-1,0);//int i=put+2
    for(RI i=1;i<=tot;i++)
    for(RI j=1;j<=tot;j++)
    mp[i][j]=mp[j][i]=((stay[i]&stay[j])||((stay[i]<<1)&stay[j])||((stay[i]>>1)&stay[j]))?0:1;
    //枚举某两种状态是否能相邻,一共有三种不能的情况:上下都是1,左下、右上是1,左上、右下是1
    for(RI i=1;i<=tot;i++)
    dp[1][cnt[i]][i]=1ll;//边界,第三维用stay数组下标表示状态(最小表示法),相当于离散化 
}
int main()
{
    scanf("%d%d",&n,&ks);
    init();
    for(RI i=2;i<=n;i++)
    for(RI j=0;j<=ks;j++)
    for(RI h=1;h<=tot;h++)//枚举当前行状态 
    {
        if(cnt[h]>j)
        continue;
        for(RI k=1;k<=tot;k++)//枚举上一行状态 
        {
            if(mp[k][h]&&cnt[k]+cnt[h]<=j)
            dp[i][j][h]+=dp[i-1][j-cnt[h]][k];//转移 
        }
    }
    for(RI i=1;i<=tot;i++)
    ans+=dp[n][ks][i];
    printf("%lld",ans);
    return 0;
}

Problem 3:

题目来源:原创
这里写图片描述
考场写线段树只有20分….然而还写错了文件名…注意注意
正解是将函数分块处理,并对于每一块建立差分数组,处理出各个数字在各块中出现的次数及各块元素总和
询问时整块直接返回整块信息,其余部分树状数组查询

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define lowbit(x) ((x)&-(x))
using namespace std;
typedef unsigned long long LL;
int n;
int a[100005];
LL c[100005];
int L[100005],R[100005];
int Q;
int t[405][100005];
int belong[100005];
LL sum[405];
int B,NUM;
void add(int x,LL d)
{
    while(x<=n)
    {
        c[x]+=d;
        x+=lowbit(x);
    }
}
LL ask(int x)
{
    LL ret=0;
    while(x)
    {
        ret+=c[x];
        x-=lowbit(x);
    }
    return ret;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&L[i],&R[i]);
    }
    B=sqrt(n)+1;
    NUM=(n-1)/B+1;
    for(int i=1;i<=n;i++) 
    belong[i]=(i-1)/B+1;//给函数分块~真是精妙 
    for(int i=1;i<=n;i++)
    c[i]=a[i];
    for(int i=1;i<=n;i++)//BIT储存元素 
    {
        if(i+lowbit(i)<=n)
        {
            c[i+lowbit(i)]+=c[i];
        }
    }
    for(int i=1;i<=n;i++)
    {
        t[belong[i]][L[i]]++;//差分,每当块内函数覆盖到一次元素,则该元素出现次数+1 
        t[belong[i]][R[i]+1]--; //分层处理块内函数对元素出现次数的影响 
    }
    for(int i=1;i<=NUM;i++)
    {
        for(int j=1;j<=n;j++)
        {
            t[i][j]+=t[i][j-1];//差分数组求前缀和,统计块内函数对当前序列位置次数的影响 
            sum[i]+=t[i][j]*1ULL*a[j];//统计各块内函数影响总和(元素值*元素出现次数)
        }
    }
    scanf("%d",&Q);
    while(Q--)
    {
        int type,x,y;
        scanf("%d%d%d",&type,&x,&y);
        if(type==1)
        {
            for(int i=1;i<=NUM;i++)
            {
                sum[i]-=t[i][x]*1ULL*a[x];//修改每个块的总和
                sum[i]+=t[i][x]*1ULL*y;
            }
            add(x,-a[x]);//修改BIT 
            a[x]=y;
            add(x,a[x]);
        }
        else
        {
            int Ln,Rn;
            Ln=belong[x],Rn=belong[y];
            LL ans=0;
            if(Ln==Rn)//若在同一块内 
            {
                for(int i=x;i<=y;i++) 
                {
                    ans+=ask(R[i])-ask(L[i]-1);//BIT前缀和作差 
                }
            }
            else
            {
                for(int i=Ln+1;i<Rn;i++) ans+=sum[i];//加上整块总和 
                int lim;
                lim=Ln*B;
                lim=min(lim,y);
                for(int i=x;i<=lim;i++)//加入单点 
                {
                    ans+=ask(R[i])-ask(L[i]-1);
                }
                lim=(Rn-1)*B+1;
                lim=max(lim,x);
                for(int i=lim;i<=y;i++) 
                {
                    ans+=ask(R[i])-ask(L[i]-1);
                }
            }
            printf("%llu\n",ans);
        }
    }
return 0;
}

Problem 4:

题目来源:http://www.lydsy.com/JudgeOnline/problem.php?id=1017
这里写图片描述
题目内容恰如其名…某wyh自己做完了就出上了…
非常巧妙的树形DP..不会做..
只能扔代码了…

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>

#define RI register int
using namespace std;
const int MAXN = 50 + 5;
const int MAXV = 2000 + 5;
const int INF = 1e9 + 7;

#define max(a,b) ((a) > (b) ? (a) : (b))
#define min(a,b) ((a) < (b) ? (a) : (b))

inline void read(int &x)
{
    x = 0;
    bool flag = 0;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')   flag = 1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    if(flag)    x *= -1;
}

char s[5];
int n,m,x,t,q,cnt,tot,ans;
int p[MAXN],lmt[MAXN],c[MAXN];
int f[MAXN][105][MAXV],g[MAXN][MAXV],h[MAXN][MAXV];
int first[MAXN],next[MAXN << 1],ru[MAXN],chu[MAXN];

//f[i][j][k] 第i种物品,j件用来合成,花钱k,最大价值 
//g[i][j]  以某点x为根,前i棵子树,花钱j,最大价值 

struct edge
{
    int f,t,v;
}l[MAXN << 1];

inline void build(int ff,int t,int v)
{
    l[++ cnt] = (edge){ff,t,v};
    next[cnt] = first[ff];
    first[ff] = cnt;
    ru[t] ++,chu[ff] ++; 
}

void init()
{
    cnt = 0;
    memset(first,-1,sizeof(first));
    memset(f,-0x3f3f3f3f,sizeof(f));
}

void solve(int x)
{
    if(!chu[x])
    {
        //更新叶子节点(低级) 
        lmt[x] = min(lmt[x],m / c[x]);
        for(int i = 0;i <= lmt[x];i ++)
            for(int j = i;j <= lmt[x];j ++)
                f[x][i][j * c[x]] = p[x] * (j - i);
        return;
    }
    //更新花费和数量,转换为背包问题 
    lmt[x] = INF,c[x] = 0;
    for(int i = first[x];i != -1;i = next[i])
    {
        int v = l[i].t;solve(v);
        c[x] += c[v] * l[i].v;
        lmt[x] = min(lmt[x],lmt[v] / l[i].v);
    }
    lmt[x] = min(lmt[x],m / c[x]);
    memset(g,-0x3f3f3f3f,sizeof(g));
    g[0][0] = 0;
    for(int t = lmt[x];t >= 0;t --)
    {
        int tot = 0;
        for(int i = first[x];i != -1;i = next[i])
        {
            tot ++;
            int v = l[i].t;
            for(int j = 0;j <= m;j ++)
                for(int k = 0;k <= j;k ++)
                    g[tot][j] = max(g[tot][j],g[tot - 1][j - k] + f[v][t * l[i].v][k]); 
        } 
        for(int j = 0;j <= t;j ++)
            for(int k = 0;k <= m;k ++)
                f[x][j][k] = max(f[x][j][k],g[tot][k] + p[x] * (t - j));
    } 
}

int main()
{
    freopen("fangak.in","r",stdin);
    freopen("fangak.out","w",stdout);
    init();
    read(n),read(m);
    for(RI i = 1;i <= n;i ++)
    {
        read(p[i]);
        scanf("%s",s +1);
        if(s[1] == 'A')
        {
            read(x);
            while(x --)
            {
                read(t),read(q);
                build(i,t,q);
            }
        }
        else    read(c[i]),read(lmt[i]);
    }
    int tot = 0,ans = -INF;
    for(int x = 1;x <= n;x ++)
        if(!ru[x])
        {
            solve(x);tot ++;
            for(int i = 0;i <= m;i ++)
                for(int j = 0;j <= i;j ++)
                    for(int k = 0;k <= lmt[x];k ++)
                        h[tot][i] = max(h[tot][i],h[tot - 1][j] + f[x][k][i - j]);
        }
    for(int i = 0;i <= m;i ++)
        ans = max(ans,h[tot][i]);
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值