• 【DP,求方案】AcWing 316. 减操作


    分析

    这题可以拆成两道题来做:

    第一题

    首先,看到这个合并顺序,感觉十分不好下手,那么我们不妨绕过对合并的分析,看看问题能等价为什么:

    随便写几个柿子,比如 \(1-((1-4)-(5-(1-4)))\),发现运算的结果可以化到最简表述为没有括号只有 +- 的形式,而且加减在除了第一个位置必然是(因为最后合并的一定需要包括位置 \(1\))每个位置都可以出现,不妨大胆猜想:问题等价于——除了第一个位置,其他位置可以任意放置 +/-

    下面是证明,感觉显然的话可以绕过。

    证明:给你一个长度为 \(n\) 的序列,支持在中间的 \(n-1\) 个位置添加 +/-,其中第一个一定为 -,可以将其等价为支持选择邻项作减法的序列。
    采用数学归纳法,记长度为 \(k\)

    • 显然 \(k = 2, 3\) 时成立。
    • 假设 \(k = n\) 时成立。
    • 对于 \(k = n + 1\),由归纳假设,后缀 \(n\) 项能够表述为支持在中间的 \(n-1\) 个位置任意添加 +/-,其中第一个一定为 -的序列。

    为了方便表述,这里对 \(k=5\) 的情况(A B C D E)作解释:
    首先 A, B 间一定为 -A - B C D E
    B C 之间的符号作分类讨论:

    1. B + C:那么我们就规定合并顺序为 A - (B - C D E),可以发现后缀四项就是 \(k=4\) 的情况,满足归纳假设。
    2. B - C:那么就率先合并 A B,情况化归为 \(k = 4\)

    可以发现上面的归纳过程正好能够为我们构造方案提供帮助。

    第二题

    背包 dp。
    这题没什么需要注意的,就每个物品分别当成是正体积和负体积的 \(01\) 背包问题处理即可,当然因为数组下标非负,我们需要加一个足够大的偏移量,这里规定是 \(100\times 100\)
    注意到需要输出方案,所以我们记录一下每个状态的前驱 pre,然后利用 pre 得到 +- 序列并构造出解即可。(构造出解的思路可以见上面的证明)。

    // Problem: 减操作
    // Contest: AcWing
    // URL: https://www.acwing.com/problem/content/318/
    // Memory Limit: 64 MB
    // Time Limit: 1000 ms
    // 
    // Powered by CP Editor (https://cpeditor.org)
    
    #include<bits/stdc++.h>
    using namespace std;
    
    #define debug(x) cerr << #x << ": " << (x) << endl
    #define rep(i,a,b) for(int i=(a);i<=(b);i++)
    #define dwn(i,a,b) for(int i=(a);i>=(b);i--)
    #define pb push_back
    #define all(x) (x).begin(), (x).end()
    
    #define x first
    #define y second
    using pii = pair<int, int>;
    using ll = long long;
    
    inline void read(int &x){
        int s=0; x=1;
        char ch=getchar();
        while(ch<'0' || ch>'9') {if(ch=='-')x=-1;ch=getchar();}
        while(ch>='0' && ch<='9') s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
        x*=s;
    }
    
    const int N=110, M=20005, del=10000;
    
    int n, V;
    int w[N];
    bool f[N][M];
    pii pre[N][M];
    
    bool in(int val){
    	return val>=0 && val<M;
    }
    
    string str;
    
    void get_str(int x, int y){
    	if(x==1) return;
    	auto [tx, ty]=pre[x][y];
    	get_str(tx, ty);
    	if(ty<y) str+="+";
    	else str+="-";
    }
    
    void print(int u, int d, bool fl){
    	if(u==n) return;
    	if(u+1==n){
    		cout<<u-d<<endl;
    		return;
    	}
    	bool nx=(str[u+1]=='+')^fl;
    	if(nx) print(u+1, d, fl^1), cout<<u-d<<endl;
    	else cout<<u-d<<endl, print(u+1, d+1, fl);
    }
    
    int main(){
    	cin>>n>>V;
    	rep(i,1,n) read(w[i]);
    	
    	f[1][w[1]+del]=1;
    	rep(i,2,n){
    		int t=w[i];
    		if(i>2){
    			rep(j,0,M-1) if(in(j-t) && !f[i][j] && f[i-1][j-t]){
    				f[i][j]=1;
    				pre[i][j]={i-1, j-t};
    			}
    		}
    		
    		t=-t;
    		rep(j,0,M-1) if(in(j-t) && !f[i][j] && f[i-1][j-t]){
    			f[i][j]=1;
    			pre[i][j]={i-1, j-t};
    		}
    	}
    	
    	get_str(n, V+del);
    	str=" "+str;
    
    	print(1, 0, 0);
    	
    	return 0;
    }
    
  • 相关阅读:
    c语言 作用域、存储期、链接属性汇总
    进程上下文切换分析
    进程装载过程分析(execve系统调用分析)
    fork 创建进程的过程分析
    系统调用软中断处理程序system_call分析
    linux 系统调用分析
    8分钟带你深入浅出搞懂Nginx
    控制反转
    JAVA泛型(转)
    AOP(转)
  • 原文地址:https://www.cnblogs.com/Tenshi/p/16225255.html
Copyright © 2020-2023  润新知