1. 程式人生 > 其它 >Codeforces 1455G - Forbidden Value(map 啟發式合併+DP)

Codeforces 1455G - Forbidden Value(map 啟發式合併+DP)

map 啟發式合併優化樹形 dp

Codeforces 題面傳送門 & 洛谷題面傳送門

首先這個 ifend 配對的結構顯然形成一個樹形結構,考慮把這棵樹建出來,於是這個程式的結構就變為,對樹進行一遍 DFS,到達某個節點時,按照順序遍歷其所有兒子,如果兒子是葉子節點則執行賦值操作,否則進入到對應子樹中執行相應操作,如果一個節點所有兒子對應的操作都被執行了就回溯,不難發現該過程與原程式等價。

考慮樹形 dp。可以發現對於一棵非葉節點,在首次進入這棵子樹時,程式中 \(x\) 的值已經確定了——就是 if v\(v\) 的值。因此設 \(dp_{x,j}\) 表示最少花費多少的代價,才能使執行完 \(x\) 子樹內的操作後,程式中變數的值為 \(j\)

,並且程式中變數的值時刻不等於 \(s\)。對於一個子樹而言,初始 \(dp_{x,v}=0\),其餘 \(dp_{x,i}=\infty\),其中 \(v\) 為進入這棵子樹時變數的值,那麼我們按順序遍歷這個節點所有子節點 \(y\),分情況討論:

  • 該子節點為賦值操作:那麼不妨假設該子節點將變數的值改為 \(p\),刪除該操作需要 \(c\) 的代價,那麼:
    • \(dp_{x,i}\leftarrow dp_{x,i}+c(i\ne p)\)
    • \(dp_{x,i}\leftarrow\min\{dp_{x,j}\}(i=p)\)
  • 該子節點不是賦值操作:假設這個子樹對應的語句為 if v,那麼有:
    • \(dp_{x,i}\leftarrow \min(dp_{x,i},dp_{x,v}+dp_{y,i})(i\ne v)\)
    • \(dp_{x,i}\leftarrow dp_{x,v}+dp_{y,i}(i=v)\)

每更新完一步之後都要把 \(dp_{x,s}\) 改為 \(\infty\),因為要保證變數的值時刻不等於 \(s\)

直接做是平方的。可以發現對於某個 \(x\) 而言,有效的 \(dp_{x,i}\) 的個數是 \(\mathcal O(\text{size}_x)\) 級別的,這不禁讓我們想到啟發式合併。我們考慮用個 map 維護所有 DP 值。對於一個是賦值操作的兒子 \(y\),我們考慮對其打一個整體加 \(+c\) 的 tag,然後用個 multiset 維護其所有 DP 值,取出 multiset

中最小的元素更新 \(dp_{x,j}\)。對於一個非賦值操作的兒子 \(y\),我們考慮啟發式合併如果該節點 \(dp\) 陣列的大小 \(>y\) 節點 \(dp\) 陣列的大小就暴力用 \(dp_y\) 中的值去更新 \(dp_x\),否則交換 \(dp_x,dp_y\),對 \(dp_x\) 打上一個 \(+dp_{x,v}\) 的標記,然後暴力將現 \(dp_y\) 也就是原 \(dp_x\) 中的元素插入 \(dp_x\) 即可。

時間複雜度 \(n\log^2n\)。細節略有一點點多。

const int MAXN=2e5;
const ll INF=0x3f3f3f3f3f3f3f3fll;
int n,m=1,k,ban,cur=1,val[MAXN+5],fa[MAXN+5];
vector<pii> to[MAXN+5];
int cst[MAXN+5],asv[MAXN+5];
ll tag[MAXN+5];
map<int,ll> dp[MAXN+5];
multiset<ll> st[MAXN+5];
ll get(int x,int v){return (dp[x].count(v))?dp[x][v]:INF;}
void relax(int x){
	if(dp[x].count(ban)){
		st[x].erase(st[x].find(dp[x][ban]));
		dp[x][ban]=INF;st[x].insert(INF);
	}
}
void dfs(int x){
//	printf("dfs %d\n",x);
	dp[x][val[x]]=0;st[x].insert(0);relax(x);
	for(pii p:to[x]){
		int op=p.fi,id=p.se;
		if(op==1){
			ll v=min((*st[x].begin())+tag[x],INF);tag[x]+=cst[id];
			if(dp[x].count(asv[id])) st[x].erase(st[x].find(dp[x][asv[id]]));
			dp[x][asv[id]]=v-tag[x];st[x].insert(v-tag[x]);
		} else {
			dfs(id);ll nd=get(x,val[id])+tag[x];
			if(dp[x].size()>dp[id].size()){
				for(pair<int,ll> pp:dp[id]){
					int pos=pp.fi;ll v=pp.se+tag[id]+nd,ori=get(x,pos)+tag[x];chkmin(v,INF);
//					printf("%d %d %lld\n",id,pp.fi,v);
					if(dp[x].count(pos)) st[x].erase(st[x].find(dp[x][pos]));
					if(pos==val[id]) dp[x][pos]=v-tag[x];
					else dp[x][pos]=min(v,ori)-tag[x];
					st[x].insert(dp[x][pos]);
				}
			} else if(nd<INF) {
//				printf("%lld\n",nd);
				swap(dp[x],dp[id]);swap(st[x],st[id]);
				swap(tag[x],tag[id]);tag[x]+=nd;
				for(pair<int,ll> pp:dp[id]){
					int pos=pp.fi;ll v=min(pp.se+tag[id],INF);
					if(pos!=val[id]){
						chkmin(v,get(x,pos)+tag[x]);
						if(dp[x].count(pos)) st[x].erase(st[x].find(get(x,pos)));
						dp[x][pos]=v-tag[x];st[x].insert(dp[x][pos]);
					}
				}
			}
		} relax(x);
//		for(pair<int,ll> pp:dp[x]) printf("dp[%d][%d]=%lld\n",x,pp.fi,pp.se+tag[x]);
	}
}
int main(){
	scanf("%d%d",&n,&ban);val[1]=0;
	for(int i=1;i<=n;i++){
		static char buf[10];scanf("%s",buf+1);
		if(buf[1]=='s'){
			int x,v;scanf("%d%d",&x,&v);
			cst[++k]=v;asv[k]=x;to[cur].pb(mp(1,k));
		} else if(buf[1]=='i'){
			int x;scanf("%d",&x);val[++m]=x;
			to[cur].pb(mp(2,m));fa[m]=cur;cur=m;
		} else cur=fa[cur];
	}
//	for(int i=1;i<=m;i++) for(pii p:to[i])
//		printf("%d -> %d %d\n",i,p.fi,p.se);
	dfs(1);printf("%lld\n",(*st[1].begin())+tag[1]);
	return 0;
}