• 【[USACO13NOV]没有找零No Change】


    其实我是点单调队列的标签进来的,之后看着题就懵逼了

    于是就去题解里一翻,发现楼上楼下的题解说的都好有道理,
    f[j]表示一个再使用一个硬币就能到达i的某个之前状态,b[now]表示使用那个能使状态j变到i的硬币的面值,find表示这些花费可以到达的最大距离,由于前缀和保持单调可以用二分求解,方程不就是f[i]=max(f[i],find(p[f[j]]+b[now]))吗

    但这道题怎么用单调队列优化呢

    我们观察这个方程你会发现无论是b[now],p[f[j]]都跟i没有什么关系,而只要是p[f[j]]+b[now]越大,相应的find的值也就越大

    于是我们就可以愉快的单调队列优化这个dp了,用一个单调队列把每次的p[f[j]]+b[now]存起来,每次有新元素入队时维护队列的单调性,之后当所有元素入队完,直接用队首的最大值进行一遍find就好了,这样就可以避免进行多次find了

    尽管单调队列是用常数奇大的deque实现的,但开了O2能跑到120 ms,轻松卡到最优解第一

    #include<iostream>
    #include<queue>
    #include<cstring>
    #include<cstdio>
    #define re register
    #define int long long
    #define maxn 100001
    using namespace std;
    int f[65540],a[maxn],b[17],n,m,num,p[maxn];
    int c[17];
    inline void check(int x)
    {
        memset(c,0,sizeof(c));
        int pp=m;
        while(x)
        {
            if(x&1) c[pp]=1;
            pp--;
            x>>=1;
        }
    }
    inline int read()
    {
        char c=getchar();
        int x=0;
        while(c<'0'||c>'9') c=getchar();
        while(c>='0'&&c<='9')
          x=(x<<3)+(x<<1)+c-48,c=getchar();
        return x;
    }
    inline int find(int x)
    {
        int l=1,r=n,tot=0;
        while(l<=r)
        {
            int mid=l+r>>1;
            if(p[mid]<=x) l=mid+1,tot=mid;
            else r=mid-1;
        }
        return tot;
    }
    signed main()
    {
        m=read();
        n=read();
        for(re int i=1;i<=m;i++) b[i]=read(),num+=b[i];
        for(re int i=1;i<=n;i++) a[i]=read();
        p[1]=a[1];
        for(re int i=2;i<=n;i++) p[i]=p[i-1]+a[i];
        for(re int i=0;i<=(1<<m)-1;i++)
        {
        	deque<int> q;
        	for(re int j=1;j<=m;j++)
        	{
            	if(!(i&(1<<(m-j)))) continue;//这里跟楼上楼下几篇题解不太一样,这个状态转成二进制后左边第一位表示的是第一个硬币是否被选择
            	int k=i^(1<<(m-j));
            	while(q.size()&&q.back()<p[f[k]]+b[j]) q.pop_back();//跟队尾元素比较,如果比队尾大就弹出队尾,维护单调队列单调性
            	q.push_back(p[f[k]]+b[j]);//入队
     		}
            //其实这里不用单调队列优化也是可以的,我们只需要存储一下最大值就好了,这样应该还能快一些,但是用单调队列优化dp的这种思路还是比较重要的
     		f[i]=find(q.front());//只用一遍find就好了
        }
     	int ans=-1;
     	for(re int i=0;i<=(1<<m)-1;i++)
     	{
     		if(f[i]!=n) continue;//当前状态根本到不了n,就直接下一个
     		check(i);//将当前的状态转成二进制数
     		int tot=0;
     		for(re int j=1;j<=m;j++)
     		if(c[j]==0) tot+=b[j];
            //如果没有被选择,那么就把它加上
     		if(tot>ans) ans=tot; 
        }
        cout<<ans;
        return 0;
    }
    
  • 相关阅读:
    GRUB引导——menu.lst的写法
    条形码类型及常见条形码介绍
    Tmux:终端复用器
    find+*的问题
    find命令之exec
    Linux core 文件介绍
    C语言中返回字符串函数的四种实现方法
    C语言中的volatile
    Stars
    Game with Pearls
  • 原文地址:https://www.cnblogs.com/asuldb/p/10207855.html
Copyright © 2020-2023  润新知