1 条题解

  • 1
    @ 2025-11-8 15:15:10

    状压dp

    ‌问题分析

    • 有N个游戏币(1≤N≤16),M个顺序关卡(状压的算盘都崩脸上了)
    • 游戏币可以任意顺序使用,关卡必须按顺序
    • 目标是最大化剩余游戏币的总价值

    思路

    因为游戏币顺序随便,考虑枚举当前已经用了哪些游戏币

    • 状态设计:‌dp[i]表示使用i对应的游戏币组合时,能闯到的最大关卡数(闯的越多,浪费的越少)

    i是一个n位二进制数,1表示用过,0表示没用过

    • 预处理:对每个游戏币,预处理从各关卡开始能闯到的最大关卡

    lef[j][st]:游戏币j,从st关开始闯,最多闯完第多少关,j和st见下文

    • 状态转移‌:枚举最后使用的游戏币进行转移
    • 答案统计‌:当某个状态能闯完所有关卡时,统计剩余游戏币价值

    转移具体实现

    已知一个状态i,枚举最后一次用的游戏币j ((i&(1<<(j-1)))==1)(翻译成人话:j在i状态下被选了)

    st = dp[(i^(1<<(j-1)))]+1;

    dp[i] = max(dp[i], lef[j][st]);

    代码也有一些注释辅助理解

    code

    bool M1;
    #include <bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define look_memory cerr<<abs(&M1-&M2)/1024.0/1024<<"MB\n";
    
    namespace syr
    {
    	const ll N = 20;
    	const ll M = 1e5+10;
    	const ll P = (1<<16)+10;
    	ll n, m, ans;
    	ll a[N], b[M];
    	ll lef[N][M]; //第i个币,从j关开始闯,闯完第几关 
    	ll dp[P]; //用了哪些币 闯完第几关 
    	void init () {
    		for (ll i=1; i<=n; i++) { //枚举币 
    			for (ll l=1, r=0; l<=m; l++) { //可以从l闯到r关 
    				while (b[r]-b[l-1]<=a[i] && r<=m) r++; //能闯就一路闯下去(反正找的是最多闯到哪) 
    				r--; //第r关是闯不完的,要-- 
    				if (l>r) continue; //一关都闯不了(太惨了TAT) 
    				lef[i][l] = r; 
    			}
    		}
    	} 
    	void update (ll i) { //更新答案 
    		ll sum = 0; //剩余的点数  
    		for (ll k=1; k<=n; k++)
    			if (!(i&(1<<(k-1)))) sum+=a[k]; //没用这个币,就把他加到剩余里  
    		ans = max(ans, sum); //更新剩余点数最大值 
    	}
    	void work()
    	{
    		cin>>n>>m;
    		for (ll i=1; i<=n; i++) cin>>a[i];
    		for (ll i=1; i<=m; i++) {
    			cin>>b[i];
    			b[i] += b[i-1]; //前缀和处理闯关的消耗(方便后面双指针用) 
    		}
    		init(); //双指针预处理  
    		memset(dp, -0x3f, sizeof(dp)); //一定不要初始化成0!!!(因为答案可能为0) 
    		ans = dp[0]; //给ans初始化极小值  
    		dp[0] = 0; //一个币都不用闯完0关 
    		for (ll i=1; i<=n; i++) //初始化用一个币的情况 
    			dp[(1<<(i-1))] = lef[i][1]; //i从1开始的 (1==(1<<0)) 所以是1<<(i-1) 
    		for (ll i=1; i<(1<<n); i++) { //用了的游戏币  
    			for (ll j=1; j<=n; j++) { //最后用的哪个 
    				if (!(i&(1<<(j-1)))) continue;
    				ll st = dp[(i^(1<<(j-1)))]+1; //从哪一关开始闯,因为上次已经闯完这一关了, 所以+1  
    				dp[i] = max(dp[i], lef[j][st]); //最多闯完第几关 
    				if (dp[i]>=m) update(i);
    			}
    		}
    		if (ans>=0) cout<<ans; //注意是>=(有'='!!!!)(怎么不能一个币都不剩呢。。。) 
    		else cout<<-1;
    	}
    }
    
    bool M2;
    
    int main()
    {
    //	freopen("game.in", "r", stdin);
    //	freopen("game.out", "w", stdout);
    	cin.tie(0)->sync_with_stdio(0);
    	look_memory;
    	syr::work();
    	return 0;
    }
    

    信息

    ID
    582
    时间
    1000ms
    内存
    256MiB
    难度
    7
    标签
    (无)
    递交数
    42
    已通过
    12
    上传者