1. 程式人生 > 其它 >【DP,求方案】AcWing 316. 減操作

【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;
}