• 【PNR#2 Div1 D】找零(DP)(贪心)


    找零

    题目链接:PNR#2 Div1 D

    题目大意

    有 500,100,50,10,5,1 这些面额的纸币,你有 X 块钱,使用最少的纸币数表示的。
    然后有一些物品,每种只有一个,有费用。
    每次你可以选择一些没买过的买,付一定的钱,然后会找你钱,用最小的纸币数。
    然后要你最大化最后 1 元纸币的数量。

    思路

    首先你肯定不会自己交 1 1 1 元的,显然不会变回来更多的。
    然后我们还有一个事情就是我们只会一个一个的买。

    因为你想要的是就是 1 1 1,那你 ( x + y )   m o d   5 ⩽ x   m o d   5 + y   m o d   5 (x+y)\bmod 5\leqslant x\bmod 5+y\bmod 5 (x+y)mod5xmod5+ymod5 这是显然的。

    然后你会发现你 500 , 100 , 50 , 10 500,100,50,10 500,100,50,10 其实都不重要,我们都可以把它当做是 5 5 5
    也就是我们只需要当做只有 5 , 1 5,1 5,1
    那就相当于每个物品有自己的费用和贡献,要你 DP 使得贡献和最大。

    自此已经可以得到 O ( n 2 ) O(n^2) O(n2) 的背包做法。
    考虑优化,发现区别于普通背包的点是贡献只有 0 , 1 , 2 , 3 , 4 0,1,2,3,4 0,1,2,3,4 五种。
    (你说 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4 四种也行)

    这里其实有很多方法,先说我写的这个:
    你考虑贪心,那就是我们肯定要让每个贡献需要的费用最小。
    那考虑按这个排序然后选,但是显然需要调整。

    不过同一个贡献的我们肯定是按费用从小到大选这个肯定没错。
    那我们要改变的就是这个贡献选的数量。
    那你设置一个波动 B B B,那如果贪心选的数量是 x x x,那我们就只需要 DP x − B ∼ x + B x-B\sim x+B xBx+B 的结果。
    然后复杂度大概是 O ( n + B 2 ) O(n+B^2) O(n+B2),那你 B B B 肯定是在能通过的时间内越大越好,这里直接取了 3000 3000 3000

    然后是别的一些方法口胡一下:
    直接设 f i , j f_{i,j} fi,j 为处理好小于等于 i i i 的贡献获得 j j j 的价值需要的最小代价,然后好像是有决策单调性的,可以 O ( n log ⁡ n ) O(n\log n) O(nlogn) 转移一步。
    也可以类似于上面的贪心,把每种贡献打包成 12 ( l c m ( 1 , 2 , 3 , 4 ) ) 12({\rm lcm}(1,2,3,4)) 12(lcm(1,2,3,4)),再枚举 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4 这四种贡献选的值模 12 , 6 , 4 , 3 12,6,4,3 12,6,4,3 的值,强制选调之后再贪心。

    还可以把 1 , 3 1,3 1,3 分一组, 2 , 4 2,4 2,4 分一组(反正分成两组),算出算出内部选的优情况,然后再双指针类似扫两组的组合。

    代码

    #include
    #include
    #include
    #include
    #define ll long long
    
    using namespace std;
    
    const ll N = 1e5 + 100;
    const ll B = 3000;
    ll n, X, f[N * 5];
    pair <ll, ll> g[N];
    vector <ll> A[5];
    ll may[5];
    
    //x.first/x.second
    bool cmp(pair <ll, ll> x, pair <ll, ll> y) {
    	return x.first * y.second < y.first * x.second;
    }
    
    int main() {
    	scanf("%lld %lld", &n, &X);
    	for (ll i = 1; i <= n; i++) {
    		ll a; scanf("%lld", &a);
    		ll x = (a + 4) / 5; ll y = x * 5 - a;
    		g[i] = make_pair(x, y);
    		A[y].push_back(x);
    	}
    	for (ll i = 0; i < 5; i++) sort(A[i].begin(), A[i].end());
    	
    	sort(g + 1, g + n + 1, cmp);
    	ll num = X / 5;
    	for (ll i = 1; i <= n; i++) {
    		if (num < g[i].first) break;
    		num -= g[i].first; may[g[i].second]++;
    	}
    	num = X / 5; ll an = X % 5, sum = 0;
    	memset(f, 0x7f, sizeof(f)); f[0] = 0;
    	for (ll sz = 0; sz < 5; sz++) {
    		ll l = max(1ll, may[sz] - B), r = min((ll)A[sz].size(), may[sz] + B);
    		for (ll i = 1; i < l; i++) {
    			an += sz; num -= A[sz][i - 1];
    		}
    		for (ll i = l; i <= r; i++) {
    			for (ll j = sum; j >= 0; j--) {
    				f[j + sz] = min(f[j + sz], f[j] + A[sz][i - 1]);
    			}
    			sum += sz;
    		}
    	}
    	
    	for (ll i = sum; i >= 0; i--)
    		if (f[i] <= num) {
    			an += i; break;
    		}
    	printf("%lld", an);
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
  • 相关阅读:
    si446使用记录(一):基本资料获取
    Maven - 3、详解maven解决依赖问题
    视频怎么加水印?这里有你想要的答案
    第十一章《Java实战常用类》第8节:Arrays类
    Unity的碰撞检测(三)
    英语翻译软件-批量自动免费翻译软件支持三方接口翻译
    【限时免费】20天拿下华为OD笔试之【哈希集合】2023B-明明的随机数【欧弟算法】全网注释最详细分类最全的华为OD真题题解
    化工行业分析:我国碳酸亚乙烯酯市场出口量为544.14吨,同比增长3.47%
    qiankun-boot 一个开箱即用的 qiankun cli
    C语言力扣刷题13——最大子数组和[线性动态规划]
  • 原文地址:https://blog.csdn.net/weixin_43346722/article/details/127561436