近期部分做题记录

近期看的东西比较多也比较杂,遇到很多想做的题目,以此博客来记录以免遗漏。

 

2019ICPC南京网络赛D

给你一个DAG,保证只有1没入边,只有n没出边。每天做一个选择:以等概率走向相邻边或者不动。第i天的花费是i。问你从1走到n的期望花费。

因为是DAG,我们可以直接在上面做DP来求期望。要求花费,期望公式就是:

dp[u] = \sum dp[v] * \frac{1}{out[u] + 1} + \frac{1}{out[u] + 1} * dp[u] + cost;

dp[u]表示u到n的期望花费

那么问题就变成了如何求cost,其实也好求,再用一个DP求出期望天数就好了:

dp2[u] = \sum dp2[v] * \frac{1}{out[u] + 1} + \frac{1}{out[u] + 1} * dp2[u] + 1;

dp2[u]表示u到n的期望天数

dp2[u]就是上面对应的cost,道理也很简单。

(写这个好像只是为了练一下LaTex?)

Code:

vector<int> G[maxn];
int out[maxn];
db dp1[maxn], dp2[maxn];
int n, m;

void init(){
	rep(i, 1, n) dp1[i] = dp2[i] = 0, out[i] = 0, G[i].clear();
}

db dfs1(int u){
	if(dp1[u]) return dp1[u];
	if(!out[u]) return 0;
	db sum = 0;
	for(int v : G[u]){
		sum += dfs1(v);
	}
	dp1[u] = sum / out[u] + 1 + 1.0 / out[u];
	return dp1[u];
}

db dfs2(int u){
	if(dp2[u]) return dp2[u];
	if(!out[u]) return 0;
	db sum = 0;
	for(int v : G[u]){
		sum += dfs2(v);
	}
	dp2[u] = sum / out[u] + dp1[u] * (1 + 1.0 / out[u]);
	return dp2[u];
}

int main()
{
	int T; scanf("%d", &T);
	while(T--){
		scanf("%d %d", &n, &m);
		init();
		rep(i, 1, m){
			int u, v; scanf("%d %d", &u, &v);
			out[u]++;
			G[u].pb(v);
		}
		dfs1(1);
		dfs2(1);
		printf("%.2f\n", dp2[1]);
	}
	return 0;
}

也可以写在一个dfs里面,懒得改了。

 

IOI 2005 河流

中文就不说题意了。

很明显的树形DP,主要谈谈如果构造状态。根据经验,会先构造出dp[i][j],表示以i为根的子树中造了j个伐木厂的最小花费。

但是这样子是转移不出来的,因为不知道一个节点的花费贡献应该是多少,所以考虑再加入一维,表示该点的木头运送到哪个点,这样子花费就能算出来了。然后对于每一个节点的子树,都是做一次树形背包:有子树大小这么多的点,选择当前枚举的这么多个点当成伐木厂的最小花费。在写之前算一算时间:照这样子写dfs里面有4层循环,看上去复杂度有 O(n^{4}k) 接受不了,根据经验树形DP的复杂度往往不是表面的那样。考虑状态量和转移量,其实也就是 O(n^{3}k) 的复杂度,刚好可以通过这题。

来写写转移方程,转移方程自然就是子树合并啦

我们开两个数组来记录:

f[i][j][k]//i点为根的子树中,建造了k个伐木厂,有一个祖先j有伐木厂,i点建造伐木厂时的最小花费
g[i][j][k]//i点为根的子树中,建造了k个伐木厂,有一个祖先j有伐木厂,i点没有建造伐木厂时的最小花费

那么具体的转移就是:

f[u][j][k] = qmin(f[u][j][k], f[u][j][k-x] + f[v][u][x]);
g[u][j][k] = qmin(g[u][j][k], g[u][j][k-x] + g[v][j][x]);
//x为枚举的儿子的伐木厂的个数

再考虑初始化:

f[u][j][k] += f[v][u][0];
g[u][j][k] += g[v][j][0];

还要注意的一点就是在一个节点的状态跑完了回溯回去之后,我们要用的是它的贡献,但是显然分类该点有没有伐木厂贡献是不全的,所以还要合并一下f和g数组,把新的花费的贡献也加进去。

接下来就是完整代码啦~

---------------把爷整吐了,明天再写------------------

哎昨天太傻了,把u写成了i,今天改了调一调细节就过了。

int n, K;
int val[105], sum[105];
vector<PII> G[105];
int acst[105], tot = 0;
int f[105][105][55], g[105][105][55];
int sz[105];

void work(int u){
	sz[u] = 1;
	for(PII nxt : G[u]){
		int v = nxt.fi, w = nxt.se;
		sum[v] = sum[u] + w;
		work(v);
		sz[u] += sz[v];
	}
}

void dfs(int u){
	acst[++tot] = u;
	int up1 = qmin(K, sz[u]);
	for(PII nxt : G[u]){
		int v = nxt.fi;
		dfs(v);
		rep(i, 1, tot){
			int j = acst[i];
			Rep(k, up1, 0){
				f[u][j][k] += g[v][u][0];
				g[u][j][k] += g[v][j][0];
				int up2 = qmin(sz[v], k);
				rep(x, 0, up2){
					f[u][j][k] = qmin(f[u][j][k], f[u][j][k-x] + g[v][u][x]);
					g[u][j][k] = qmin(g[u][j][k], g[u][j][k-x] + g[v][j][x]);
				}
			}
		}
	}
	rep(i, 1, tot){
		int j = acst[i];
		Rep(k, up1, 0){
			if(k) g[u][j][k] = qmin(g[u][j][k] + val[u] * (sum[u] - sum[j]), f[u][j][k-1]);
			else g[u][j][k] += val[u] * (sum[u] - sum[j]);
		}
	}
	tot--;
}

int main()
{
	scanf("%d %d", &n, &K);
	rep(i, 1, n){
		int v, d; scanf("%d %d %d", val + i, &v, &d);
		G[v].pb(mp(i, d));
	}
	work(0);
	dfs(0);
	printf("%d\n", g[0][0][K]);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值