Codeforces 147B Smile House(DP预处理 + 倍增)

题目链接  Smile House

题意  给定一个$n$个点的有向图,求一个点数最少的环,使得边权之和$>0$,这里的环可以重复经过点和边。

    满足  $n <= 300$

首先答案肯定是单调的,但是观察发现只有当我们给所有的点加一个自环的时候才满足这个性质。

考虑$DP$。设$f[i][j][k]$为长度为$i$,从$j$走到$k$能经过的最大边权和。

那么$f[i][j][k] = min(f[i-1][j][l] + g[l][k])$,这样的预处理是$O(n^{4})$的,$TLE$。

考虑$f[i][j][k]$为长度为$2^{i}$,从$j$走到$k$能经过的最大边权和。

那么$f[i][j][k] = min(f[i-1][j][l] + f[i - 1][l][k])$, 这样的预处理是$O(n^{3}logn)$的。

现在考虑二分答案。把当前验证的答案$u$拆成不同的几个$2$的幂次(最多$logn$个)。

令$u = 2^{a_{1}} + 2^{a_{2}} + 2^{a_{3}} + ... + 2 ^ {a_{m}}$

设$c[i][j]$为若只考虑长度为$2^{a_{1}}$, $2^{a_{2}}$, ...,  $2^{a_{m}}$的边,从$i$走到$j$经过的路径权值和的最大值。

那么又是一波$O(n^{3}logn)$的转移。

若最后存在$c[i][i] > 0$,则$u$可行。

时间复杂度$O(n^{3}log^{2}n)$

#include <bits/stdc++.h>

using namespace std;

#define rep(i, a, b)	for (int i(a); i <= (b); ++i)
#define dec(i, a, b)	for (int i(a); i >= (b); --i)
#define MP		make_pair
#define fi		first
#define se		second

typedef long long LL;

const int inf = 1e9;

const int N = 306;

int n, m;
int f[10][N][N], c[2][N][N];
int l, r;

bool check(int u){
	int x = 0;
	rep(i, 1, n) rep(j, 1, n) c[0][i][j] = -inf * (i != j);
	rep(st, 0, 9) if (u & (1 << st)){
		x ^= 1;
		rep(i, 1, n) rep(j, 1, n) c[x][i][j] = -inf;
		rep(k, 1, n) rep(i, 1, n) rep(j, 1, n)
			c[x][i][j] = max(c[x][i][j], c[x ^ 1][i][k] + f[st][k][j]);
	}

	rep(i, 1, n) if (c[x][i][i] > 0) return true;
	return false;
}

int main(){

	scanf("%d%d", &n, &m);

	rep(st, 0, 9) rep(i, 0, n + 1) rep(j, 0, n + 1) f[st][i][j] = -inf * (int)(i != j);
	rep(i, 1, m){
		int x, y;
		scanf("%d%d", &x, &y);
		scanf("%d%d", &f[0][x][y], &f[0][y][x]);
	}

	rep(st, 1, 9){
		rep(k, 1, n){
			rep(i, 1, n){
				rep(j, 1, n){
					f[st][i][j] = max(f[st][i][j], f[st - 1][i][k] + f[st - 1][k][j]);
				}
			}
		}
	}

	l = 2, r = n;
	if (!check(r)) return 0 * puts("0");
	while (l + 1 < r){
		int mid = (l + r) >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}

	if (check(l)) printf("%d\n", l);
	else printf("%d\n", r);
	return 0;
}

但是还有更优的办法。

我们可以联想到$O(logn)$求$LCA$时的做法。

$2$的幂次从搞到低依次判断,若当前的状态和已经存储的状态结合后可以形成正环,那么恰恰不能取这个幂次。

类比求$LCA$的过程,当$x$往上跳$2^{i}$步后到达的结点和y往上跳$2^{i}$步后到达的结点一样,那么恰恰不能往上跳$2^{i}$步。

我们可以类比这个方法求解这道题最后的答案。

也就是不能形成正环的最大值$ans$

别忘了最后$ans$得加$1$

这样相对前一种方法,复杂度少了一个$log$

时间复杂度$O(n^{3}logn)$

#include <bits/stdc++.h>

using namespace std;

#define rep(i, a, b)	for (int i(a); i <= (b); ++i)
#define dec(i, a, b)	for (int i(a); i >= (b); --i)

const int inf = 1e9;

const int N = 306;

int n, m;
int f[10][N][N], c[N][N], g[N][N];
int l, r;
int ans;

int main(){

	scanf("%d%d", &n, &m);

	rep(st, 0, 9) rep(i, 0, n + 1) rep(j, 0, n + 1) f[st][i][j] = g[i][j] = -inf * (int)(i != j);
	rep(i, 1, m){
		int x, y;
		scanf("%d%d", &x, &y);
		scanf("%d%d", &f[0][x][y], &f[0][y][x]);
	}

	rep(st, 1, 9){
		rep(k, 1, n){
			rep(i, 1, n){
				rep(j, 1, n){
					f[st][i][j] = max(f[st][i][j], f[st - 1][i][k] + f[st - 1][k][j]);
				}
			}
		}
	}

	ans = 0;
	dec(st, 9, 0){
		rep(i, 0, n + 1) rep(j, 0, n + 1) c[i][j] = -inf;
		rep(k, 1, n) rep(i, 1, n) rep(j, 1, n) c[i][j] = max(c[i][j], g[i][k] + f[st][k][j]);
		bool flag = false;
		rep(i, 1, n) if (c[i][i] > 0){ flag = true; break;}
		if (!flag){
			ans += 1 << st;
			rep(i, 0, n + 1) rep(j, 0, n + 1) g[i][j] = c[i][j];
		}
	}

	++ans;
	printf("%d\n", ans > n ? 0 : ans);
	return 0;
}

  

  

原文地址:https://www.cnblogs.com/cxhscst2/p/8419870.html

时间: 2024-12-21 20:53:21

Codeforces 147B Smile House(DP预处理 + 倍增)的相关文章

CodeForces 18E Flag 2 dp

题目链接:点击打开链接 #include<stdio.h> #include<iostream> #include<string.h> #include<set> #include<vector> #include<map> #include<math.h> #include<queue> #include<string> #include<stdlib.h> #include<a

Codeforces 41D Pawn 简单dp

题目链接:点击打开链接 给定n*m 的矩阵 常数k 下面一个n*m的矩阵,每个位置由 0-9的一个整数表示 问: 从最后一行开始向上走到第一行使得路径上的和 % (k+1) == 0 每个格子只能向或走一步 求:最大的路径和 最后一行的哪个位置作为起点 从下到上的路径 思路: 简单dp #include <cstdio> #include <algorithm> #include<iostream> #include<string.h> #include &

CodeForces 19B Checkout Assistant dp

题目链接:点击打开链接 #include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <iostream> #include <map> #include <set> #include <math.h> using namespace std; #define inf 115292150460684697

Neko and Aki&#39;s Prank CodeForces - 1152D (括号序列,dp)

大意: 将所有长度为2*n的合法括号序列建成一颗trie树, 求trie树上选出一个最大不相交的边集, 输出边集大小. 最大边集数一定不超过奇数层结点数. 这个上界可以通过从底层贪心达到, 所以就转化为求奇数层结点数. 然后就dp求出前$i$为'('比')'多j个的方案数, 奇数层且合法的时候统计一下贡献即可. #include <iostream> #include <iostream> #include <algorithm> #include <cstdio

codeforces 148D 【概率dp】

题目链接: codeforces 148D Bag of mice 题意:一个包里面有w只白老鼠和b只黑老鼠,公主与龙依次从包中拿老鼠,每次取一只,当龙拿时还会从包中溜走一只,先拿到老鼠的获胜,当背包中没老鼠时且之前没人拿到白老鼠则龙获胜,问公主获胜的概率是多少. 题解: 设dp[i][j]为背包中有i只白老鼠j只黑老鼠时公主获胜的概率 则公主获胜的情况分成三种: 1.直接拿到白老鼠 p1=i/(i+j) 2.公主拿到黑老鼠,龙拿到黑老鼠,逃跑一只黑老鼠 p2=(j/(i+j)) ((j-1)/

[Codeforces 1295F]Good Contest(DP+组合数学)

[Codeforces 1295F]Good Contest(DP+组合数学) 题面 有一个长度为\(n\)的整数序列,第\(i\)个数的值在\([l_i,r_i]\)中随机产生.问这个序列是一个不上升序列的概率(模\(998244353\)意义下). \(n \leq 50,l_i,r_i \leq 998244351\) 分析 和[APIO2016]划艇几乎一模一样.可惜比赛的时候时间不够. 首先把问题转化成求最长不上升序列的数量. 我们把这些区间离散化,分割成两两之间不互相覆盖的若干个区间

codeforces 279C C. Ladder(rmq+预处理)

题目连接: codeforces 279C 题目大意: 给出一个序列,m次查询,每次给出一个子串,问这个子串是否满足,中间能够找到一个元素,让这个元素作为前后分别单调的分界. 题目分析: 首先对于每次查询,我们知道分界一定是最大的元素,所以我们可以用rmq预处理出区间最大. 然后为了判断这个区间是否能够通过最大的元素作为分界点而前后单调,我们可以通过预处理,正向和反向分别扫一遍,记录某一个点的为最大元素的能够向左和向右得到的最长的单调子串的长度,然后每次只需要O(1)的判断就可以,判断当前元素最

Educational Codeforces Round 26 D dp,思维

Educational Codeforces Round 26 D. Round Subset 题意:有 n 个数,从中选出 k 个数,要使这 k 个数的乘积末尾的 0 的数量最多. tags:dp好题 dp[i][j][l] 表示前 i 个数,选取了其中 j 个数,分解因子后有 l 个 5时,最多有多少个 2 .i 这一维明显可以省略. 这题一开始有个地方写挫了..选取 j 个数时,应该反着来,即 for( j, k, 1) ,不是 for( j, 1, k) ,不然会多算. #include

codeforces 706C Hard problem DP(动态规划)问题

题目链接:http://codeforces.com/problemset/problem/706/C 题目大意:  给定n个字符串, 每个字符串可以颠倒前后位置(第一个字母到最后一个,第二个字母到倒数第二位) 每次颠倒需要花费ci的力气, 要求将所给的n个字符串用最小力气按字典序排列, 输出力气值, 如果无法按字典序排列, 则输出-1 数据范围:2?≤?n?≤?100?000 . ci (0?≤?ci?≤?1e9) 所有字符串总长度不会超过1000000. 解题思路: 这是一道DP题, dp[