zoukankan      html  css  js  c++  java
  • [NOIP2018 提高组] 保卫王国

    [NOIP2018 提高组] 保卫王国


    深受启发的题解

    DP的集大成者


    倍增:(O((n+m) log n))


    首先,这道题最原始的问题是经典的最大独立集问题。没有上司的舞会

    (dp(u,0))代表节点(u)没被选上,(dp(u,1))代表结点(u)被选上。

    时间复杂度为(O(n))

    对于本题的(44pts),我们只需要每次暴力修改点值,然后跑一遍最大独立集的模板,便能够轻松获得了。


    接下来,我们先将问题简单化。

    我们可以先考虑每次修改一个点的时候该怎么做;

    容易看到,对于以该点(不妨设为(u))为根的子树,给定的输入状态(指令:选还是不选,不妨设为(state))对应的dp值就是这棵子树的答案。换句话说,对于整棵子树,我们不需要重复计算它们的dp值。我们只需要将(dp(u,state))作为答案。

    那么,对于点(u)的父节点如何统计它们对答案的贡献啊?

    二次扫描与换根

    我们考虑以下方法:

    • 定义两个数组(dp1(u,0/1))(dp2(u,0/1))分别代表以(u)为根的子树最小值和整棵树中扔掉以(u)为根的这棵子树剩下的部分最小值(即不考虑以(u)为根的这棵子树计算得来的答案,注意(0 or 1)指的是(u)选不选,但不计算(u)的答案);
    • 转移:这里只举一例:(dp2(u,0)=dp2(fa_u,1)+dp1(fa_u,1)-min(dp1(u,0),dp1(u,1)))
    • 更新的时候,由于单次修改对后面没有影响,我们只需要将操作状态对应的dp值输出。

    现在,我们拓展(回到原问题上来)——每次修改两个点的状态,该怎么办。

    类比于刚刚怎么做。

    如果(u)(v)重合,那么不难发现,这就是上一个问题。

    那么,对于被修改不重合的两个点(u)(v)(u)(v)(path(u,v))(u)(v)的简单路径所构成的点集)可以看作是一个“广义节点”,类比于刚才的做法。

    由此,我们有以下的做法:

    将这些点聚合成一个“广义节点”,外部按照上述做法解决,内部单独搞独立集。

    那么“广义节点”包括哪些?是单纯将(path(u,v))当成“广义节点”,还是将路径上牵出来的子树也囊括其中?

    选择后者。

    这是因为前者求解这些子树时依赖于路径上的每一个点的选取状态,而想要求解最小值只能枚举


    问题1

    我们先考虑——树退化为一条链的情况:(u)(v)是链上的两个端点。按照之前的做法,直接将所有(u)(v)路径上的点全部缩掉。值得欢庆的是,这条路径上没有子树。

    整体而言,我们对于“广义节点”外的结点跑最大独立集,对内再来一个独立集。

    详细地讲,我们在求解“广义节点”的内部独立集问题时,必须和外面“绝缘”。换言之,我们求解内部独立集的时候,把它当做一个独立的链来统计。

    不难想到,这样做的复杂度是(O(n))的,效率极低。由此,我们不妨使用倍增的思想处理 内部矛盾。

    不妨定义(dp(u,i,0/1,0/1))代表从(u)(2^i)级祖先的最小值。

    按照正常的倍增做法去做。然后对于一条链而言,我们按二进制把它拆开来,一段一段当作求独立集时一个个结点维护即可。


    问题2

    我们再考虑,当(u)(v)的祖先时的情况。

    可以看到,在这个问题里面,与上一个子问题唯一有差异的地儿是:“广义节点”路径上是有子树的。由此,在初始化时我们要算上子树的权值。(具体实现细节,可以认真思考)。

    void dp_prework()
    {
    	FOR(i, 2, n)
    	{
    		int fa = F[i][0];
    		dp[i][0][0][1] = dp1[fa][1] - min(dp1[i][0], dp1[i][1]);
    		dp[i][0][1][0] = dp1[fa][0] - dp1[i][1];
    		dp[i][0][1][1] = dp1[fa][1] - min(dp1[i][0], dp1[i][1]);
    	}
    	FOR(j, 1, t)
    	{
    		FOR(i, 1, n)
    		{
    			int anc = F[i][j - 1];// anc -> ancestor
    			dp[i][j][0][0] = min(dp[i][j - 1][0][0] + dp[anc][j - 1][0][0], dp[i][j - 1][0][1] + dp[anc][j - 1][1][0]);
    			dp[i][j][0][1] = min(dp[i][j - 1][0][0] + dp[anc][j - 1][0][1], dp[i][j - 1][0][1] + dp[anc][j - 1][1][1]);
    			dp[i][j][1][0] = min(dp[i][j - 1][1][0] + dp[anc][j - 1][0][0], dp[i][j - 1][1][1] + dp[anc][j - 1][1][0]);
    			dp[i][j][1][1] = min(dp[i][j - 1][1][0] + dp[anc][j - 1][0][1], dp[i][j - 1][1][1] + dp[anc][j - 1][1][1]);
    		}
    	}
    	return;
    }
    

    最后的问题

    最后,我们将直接面对最一般的情况,即当不存在一个节点是另一个节点的祖先时,我们该如何处理。

    先抓住两点的最近公共祖先(记作(lca))。我们不难观察到,倘若(lca)选取状态确定了,那么这就相当于跑了两遍的“问题(2)”((u)(lca)(v)(lca))再加上(lca)上面的一大团东西。

    可是问题是(lca)选取状态不确定。没关系,我们就枚举它的“选取状态”,分别对于每种选取状态进行求解更新。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<vector>
    #include<cmath>
    #include<map>
    #define PII pair <int, int>
    #define MP make_pair
    #define RE register
    #define CLR(x, y) memset(x,y,sizeof x)
    #define FOR(i, x, y) for(RE int i=x;i<=y;++i)
    #define ROF(i, x, y) for(RE int i=x;i>=y;--i)
    using namespace std;
    
    typedef long long LL;
    
    const int MAXN = 1e5 + 5;
    const LL INF = 1e12;
    
    template <class T> void read(T &x)
    {
    	bool mark = false;
    	char ch = getchar();
    	for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') mark = true;
    	for(x = 0; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0';
    	if(mark) x = -x;
    }
    
    map <PII, bool> table;
    
    vector <int> G[MAXN];
    int n, m, t;
    int F[MAXN][30] = {}, dep[MAXN] = {};
    // F[u,i] -> u的2^i级祖先 dep[u] -> u到根节点的深度 T[u] -> u到根节点的距离的log值 
    LL dp1[MAXN][2] = {}, dp2[MAXN][2] = {}, dp[MAXN][30][2][2] = {}, p[MAXN] = {};
    // dp1[u][0/1] -> 在u子树中, 不选/选 u的最小值  dp2[u][0/1] -> 在整棵树除了u子树中, 不选/选 u的最小值  
    
    // 倍增预处理 
    void BFS()
    { 	
    	int hh = 0, tt = 0, q[MAXN] = {};
    	int u, v;
    	dep[1] = 1, q[tt ++] = 1;
    	while(hh < tt)
    	{
    		u = q[hh ++];
    		for(RE int i = 0; i < G[u].size(); ++ i)
    		{
    			v = G[u][i];
    			if(dep[v]) continue;
    			dep[v] = dep[u] + 1, F[v][0] = u;
    			FOR(i, 1, t) F[v][i] = F[F[v][i - 1]][i - 1];
    			q[tt ++] = v;
    		}
    	}
    	return;
    }
    // the prework of dp1[u, 0/1]
    void dfs_dp1(int u)
    {
    	dp1[u][0] = 0, dp1[u][1] = p[u];
    	for(RE int i = 0; i < G[u].size(); ++ i)
    	{
    		int v = G[u][i];
    		if(v == F[u][0]) continue;
    		dfs_dp1(v);
    		dp1[u][0] += dp1[v][1], dp1[u][1] += min(dp1[v][0], dp1[v][1]);
    	}
    	return;
    }
    // the prework of dp2[u, 0/1]
    void dfs_dp2(int u)// @
    {
    	int v;
    	for(RE int i = 0; i < G[u].size(); ++ i)
    	{
    		v = G[u][i];
    		if(v == F[u][0]) continue;
    		dp2[v][0] = dp2[u][1] + dp1[u][1] - min(dp1[v][0], dp1[v][1]);
    		dp2[v][1] = min(dp2[v][0], dp2[u][0] + dp1[u][0] - dp1[v][1]);
    		dfs_dp2(v);
    	}
    	return;
    }
    // the prework of all of those dp arrays
    void dp_prework()
    {
    	dfs_dp1(1), dfs_dp2(1);
    	FOR(i, 1, n)
    		FOR(j, 0, t) 
    			FOR(x, 0, 1)
    				FOR(y, 0, 1) dp[i][j][x][y] = INF;
    	FOR(i, 2, n)
    	{
    		int fa = F[i][0];
    		dp[i][0][0][1] = dp1[fa][1] - min(dp1[i][0], dp1[i][1]);
    		dp[i][0][1][0] = dp1[fa][0] - dp1[i][1];
    		dp[i][0][1][1] = dp1[fa][1] - min(dp1[i][0], dp1[i][1]);
    	}
    	FOR(j, 1, t)
    	{
    		FOR(i, 1, n)
    		{
    			int anc = F[i][j - 1];// anc -> ancestor
    			dp[i][j][0][0] = min(dp[i][j - 1][0][0] + dp[anc][j - 1][0][0], dp[i][j - 1][0][1] + dp[anc][j - 1][1][0]);
    			dp[i][j][0][1] = min(dp[i][j - 1][0][0] + dp[anc][j - 1][0][1], dp[i][j - 1][0][1] + dp[anc][j - 1][1][1]);
    			dp[i][j][1][0] = min(dp[i][j - 1][1][0] + dp[anc][j - 1][0][0], dp[i][j - 1][1][1] + dp[anc][j - 1][1][0]);
    			dp[i][j][1][1] = min(dp[i][j - 1][1][0] + dp[anc][j - 1][0][1], dp[i][j - 1][1][1] + dp[anc][j - 1][1][1]);
    		}
    	}
    	return;
    }
    LL solve(int u, bool opt1, int v, bool opt2)
    {
    	if(dep[u] > dep[v]) swap(u, v), swap(opt1, opt2);
    	LL flca[2], fu[2] = {INF, INF}, fv[2] = {INF, INF}, new_fu[2] = {INF, INF}, new_fv[2] = {INF, INF};
    	fu[opt1] = dp1[u][opt1], fv[opt2] = dp1[v][opt2];
    	ROF(i, t, 0)
    	{
    		if(dep[F[v][i]] >= dep[u])
    		{
    			new_fv[0] = new_fv[1] = INF;
    			FOR(x, 0, 1)
    				FOR(y, 0, 1)
    					new_fv[x] = min(new_fv[x], fv[y] + dp[v][i][y][x]);
    
    			FOR(x, 0, 1) fv[x] = new_fv[x];
    			v = F[v][i];
    		}
    	}
    	if(u == v) return fv[opt1] + dp2[u][opt1];
    	ROF(i, t, 0)
    	{
    		if(F[u][i] != F[v][i])
    		{
    			new_fu[0] = new_fu[1] = new_fv[0] = new_fv[1] = INF;
    			FOR(x, 0, 1)
    				FOR(y, 0, 1)
    					new_fv[x] = min(new_fv[x], fv[y] + dp[v][i][y][x]), new_fu[x] = min(new_fu[x], fu[y] + dp[u][i][y][x]);
    			
    			FOR(x, 0, 1) fu[x] = new_fu[x], fv[x] = new_fv[x];
    			u = F[u][i], v = F[v][i];
    		}
    	}
    	int lca = F[u][0];
    	flca[0] = dp2[lca][0] + dp1[lca][0] - dp1[u][1] - dp1[v][1] + fu[1] + fv[1];
    	flca[1] = dp2[lca][1] + dp1[lca][1] - min(dp1[u][0], dp1[u][1]) - min(dp1[v][0], dp1[v][1]) + min(fu[0], fu[1]) + min(fv[0], fv[1]);
    	return min(flca[0], flca[1]);		
    }
    signed main()
    {
    	read(n), read(m);
    	char type[10];
    	cin >> type;
    	t = log(n) / log(2) + 1;
    	FOR(i, 1, n) read(p[i]), G[i].clear();
    	int a, x, b, y;
    	FOR(i, 2, n)
    	{
    		read(x), read(y);
    		G[x].push_back(y), G[y].push_back(x);
    		table[MP(x, y)] = table[MP(y, x)] = true;
    	}
    	BFS(), dp_prework();
    	FOR(i, 1, m)
    	{
    		read(a), read(x), read(b), read(y);
    		if(!x && !y && table.find(MP(a, b)) != table.end()) puts("-1");
    		else printf("%lld
    ", solve(a, x, b, y));
    	}
    	return 0;
    }
    
  • 相关阅读:
    set, unordered_set模板类
    C/C++ Bug记录
    win10远程连接
    C/C++缓冲区刷新问题
    hihocoder1711 评论框排版[并查集+set]
    makefile
    Virtual Table
    粤语
    xilinx SDK开发 GPIO使用API总结
    基于zynq 7020的串口UART中断实验
  • 原文地址:https://www.cnblogs.com/zach20040914/p/14611617.html
Copyright © 2011-2022 走看看