刷题日志(3)

这篇博客记录了刷题过程中遇到的图论算法问题,包括AOJ 2249的Dijkstra改造、AOJ 2200的动态规划与Floyd结合、HDU 4725的Dijkstra与分层建图优化,以及HDU 4185和1281的最大匹配解法。还涉及CodeForces 1343E的最短路思维、POJ 3104的二分查找、Nastya和Scoreboard的预处理DP贪心策略,以及Priest John's Busiest Day的2-SAT解决方法。
摘要由CSDN通过智能技术生成

写在前面的话:我也是初学,有些分析或知识会有错误,望各位大佬们指教

1:AOJ 2249 Road Construction (Dijkstra)

原题链接

  • 题意:
    m 条边,包含它的起点,终点,长度和花费。要求在 城市一 到其他城市的最短路长度不变的条件下,挑选花费最少的路,使建设总和最少。
  • 分析:
    这是基于对 Dijkstra算法 的改造,我们在求最短路时,同时要考虑建设费用的问题,但前提是最短路的长度不改变,因此求最短路的思想不变,加上如果两条路都相等,判断一下是否可以用花费更短的路替换。最后求出建设最少费用。另外有一点要注意,更新建设费用时,与更新最短路不同,因为之前建设道路的花费已经记录下来了,所以不用加上之前的费用,否则就重复建设多条边了。
  • 代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<string>
#include<cstdio>
#include<stack>
#include<cstring>
#include<utility>
#include<climits>
#pragma warning(disable : 4996)
using namespace std;

const int N = 10010;
const int INF = 99999999;
int p[N], dis[N], tot, vis[N], n, co[N];
struct edge {
	int to, next, val, cost;
}edges[40010];

void addEdge(int from, int to, int val, int cost) {
	tot++;
	edges[tot].cost = cost;
	edges[tot].to = to;
	edges[tot].next = p[from];
	edges[tot].val = val;
	p[from] = tot;
}

void Dijkstra(int x) {
	int i, v, c, k;
	memset(vis, 0, sizeof(vis));
	for (i = 0; i <= n; i++)
	{
		dis[i] = INF;
		co[i] = INF;
	}
	dis[x] = 0;
	co[x] = 0;
	for (i = p[x]; i; i = edges[i].next)
	{
		v = edges[i].to;
		if (dis[v] > edges[i].val)
		{
			dis[v] = edges[i].val;
			co[v] = edges[i].cost;
		}
		else if (dis[v] == edges[i].val&&co[v] > edges[i].cost)
			co[v] = edges[i].cost;
	}
	for (k = 1; k < n; k++)
	{
		int minn = INF;
		int index;
		for (i = 1; i <= n; i++)
		{
			if (minn > dis[i] && vis[i] == 0)
			{
				minn = dis[i];
				c = co[i];
				index = i;
			}
			else if (minn == dis[i] && vis[i] == 0 && co[i] < c)
			{
				c = co[i];
				index = i;
			}
		}
		vis[index] = 1;
		for (i = p[index]; i; i = edges[i].next)
		{
			v = edges[i].to;
			if (vis[v] == 0 && dis[v] > dis[index] + edges[i].val)
			{
				dis[v] = dis[index] + edges[i].val;
				co[v] = edges[i].cost;
			}
			if (vis[v] == 0 && dis[v] == dis[index] + edges[i].val && co[v] > edges[i].cost)
			{
				co[v] = edges[i].cost;
			}
		}
	}
}

int main() {
	int i, x, y, z, c, m;
	while (scanf("%d%d", &n, &m), n || m)
	{
		tot = 0;
		memset(p, 0, sizeof(p));
		for (i = 0; i < m; i++)
		{
			scanf("%d%d%d%d", &x, &y, &z, &c);
			addEdge(x, y, z, c);
			addEdge(y, x, z, c);
		}
		Dijkstra(1);
		int sum = 0;
		for (i = 1; i <= n; i++)
			sum += co[i];
		printf("%d\n", sum);
	}
	system("pause");
	return 0;
}

2:AOJ 2200 Mr. Rito Post Office(动态规划 + Floyd + 暴力枚举)

原题链接

  • 题意:
    给一些边,分为陆路和水路,一个邮递员要按照给定的顺序访问不同的节点,当使用水路的时候,会把船停泊在目的地,然后下一次使用水路的时候,要先返回该地取船。问走完给定顺序的节点,最少需要多少时间。
  • 分析:
    光看题目就觉得很有难度,首先分为水路和陆路,所以要建两幅图,然后分别用 Floyd 求出各点间最短路径。其次还有船的停泊地点需要考虑,光利用平时的最短路算法根本无法操作。
    如果仅仅考虑动态规划递归求解,又难以确定上一次船停在了什么地方,所以这题需要暴力枚举出所有的情况。dp [ i ] [ j ] 中的 i 表示访问到序列中的第 i 个地点, j 表示把船停在了 j 节点。详细暴力枚举方式在代码里有注释。
  • 代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<string>
#include<cstdio>
#include<stack>
#include<cstring>
#include<utility>
#include<climits>
#pragma warning(disable : 4996)
using namespace std;

typedef long long ll;
const ll INF = 0x1f1f1f1f1f1f1f1f;
ll l[210][210], w[210][210], dp[1010][210];
int seq[1010], n, r;

int main() {
	int i, x, y, m, k, j;
	ll z;
	char ch[2];
	while (scanf("%d%d", &n, &m), n || m)
	{
		memset(l, INF, sizeof(l));
		memset(w, INF, sizeof(w));
		memset(dp, INF, sizeof(dp));
		for (i = 0; i < m; i++)
		{
			scanf("%d%d%lld", &x, &y, &z);
			scanf("%s", ch);
			if (ch[0] == 'L')
				l[x][y] = l[y][x] = z;
			else
				w[x][y] = w[y][x] = z;
		}
		scanf("%d", &r);
		for (i = 1; i <= r; i++)
			scanf("%d", &seq[i]);
		for (k = 1; k <= n; k++)
			for (i = 1; i <= n; i++)
				for (j = 1; j <= n; j++)
				{
					if (i == j || l[i][k] == INF || l[k][j] == INF)
						continue;
					l[i][j] = min(l[i][j], l[i][k] + l[k][j]);
				}
		for (k = 1; k <= n; k++)
			for (i = 1; i <= n; i++)
				for (j = 1; j <= n; j++)
				{
					if (i == j || w[i][k] == INF || w[k][j] == INF)
						continue;
					w[i][j] = min(w[i][j], w[i][k] + w[k][j]);
				}
		for (i = 1; i <= n; i++)
		{
			w[i][i] = 0;
			l[i][i] = 0;
		}
		dp[1][seq[1]] = 0;         //初始船停在序列第一个位置
		for (i = 1; i <= r; i++)   //按照序列顺序依次枚举
		{
			for (j = 1; j <= n; j++)    //枚举船停留的地方
			{
				dp[i][j] = min(dp[i][j], dp[i - 1][j] + l[seq[i - 1]][seq[i]]);   //求出走陆路,花费最短时间
				for (k = 1; k <= n; k++)                                          //枚举走水路,把船从j转移到k
					dp[i][k] = min(dp[i][k], dp[i - 1][j] + l[seq[i - 1]][j] + w[j][k] + l[k][seq[i]]);
				        //l[seq[i - 1]][j]表示从上一个地点,走到j点取船
						//w[j][k]表示把船从j点开到k点
						//l[k][seq[i]]表示走完水路后,走陆路回到序列中第i点
			}
		}
		ll ans = INF;
		for (i = 1; i <= n; i++)
			ans = min(ans, dp[r][i]);
		printf("%lld\n", ans);
	}
	system("pause");
	return 0;
}

3:HDU 4725 The Shortest Path in Nya Graph(Dijkstra + 分层建图 + 堆优化 )

原题链接

  • 题意:
    n 个节点,分布在不同的层,上下层之间各节点可以随意移动,代价为 c ,此外还存在特定 m 条边,问从 节点1节点n 代价最少为多少,如果不存在输出-1。
  • 分析:
    显而易见的最短路问题,关键是如何处理层与层之间的关系。第一种做法是各层之间的节点都建边,这种做法 Memory Limit Exceeded 了。第二种做法是记录下各个节点所在层数,利用 Dijkstra 更新边时,再考虑层与层之间的关系,这种做法 Time Limit Exceeded。最后的正解是:在各层再建立一个节点,这个节点到本层节点的代价为0,到其他节点的代价为 c ,然后加上堆优化,就 AC了。
  • 代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<string>
#include <cstdio>
#include<stack>
#include<cstring>
#include<utility>
#include<climits>
#pragma warning(disable : 4996)
using namespace std;

const int N = 200010;
const int INF = 0x3f3f3f3f;
int n, tot, c, vis[N], dis[N], p[N];
struct edge {
	int to, val, next;
}edges[N << 4];

void addedge(int from, int to, int val) {
	tot++;
	edges[tot].to = to;
	edges[tot].val = val;
	edges[tot].next = p[from];
	p[from] = tot;
}

struct sta {
	int num, val;
	sta(int m = 0, int v = 0) :num(m), val(v) { }
	bool operator < (const sta other) const {
		return val > other.val;
	}
};

int DIJKSTRA(int x, int y) {
	priority_queue<sta> que;
	int i, u, v, len;
	sta temp;
	memset(vis, 0, sizeof(vis));
	for (i = 1; i <= n; i++)
		dis[i] = INF;
	for (i = p[x]; i; i = edges[i].next)
	{
		u = edges[i].to;
		dis[u] = min(dis[u], edges[i].val);
		que.push(sta(u, dis[u]));
	}
	dis[x] = 0;
	vis[x] = 1;
	while (!que.empty())
	{
		temp = que.top();
		que.pop();
		if (vis[temp.num])
			continue;
		u = temp.num;
		vis[u] = 1;
		if (u == y)
			break;
		for (i = p[u]; i; i = edges[i].next)
		{
			v = edges[i].to;
			if (vis[v] == 0 && dis[v] > dis[u] + edges[i].val)
			{
				dis[v] = dis[u] + edges[i].val;
				que.push(sta(v, dis[v]));
			}
		}
	}
	if (dis[y] == INF)
		return -1;
	else
		return dis[y];
}

int main() {
	int m, i, x, y, z, t, kase;
	kase = 1;
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d%d%d", &n, &m, &c);
		memset(p, 0, sizeof(p));
		tot = 0;
		for (i = 1; i <= n; i++)
		{
			int l;
			scanf("%d", &l);
			addedge(l + n, i, 0);
			if (l - 1 > 0)
			{
				addedge(i, l - 1 + n, c);
				addedge(l - 1 + n, i, c);
			}
			if (l + 1 <= n)
			{
				addedge(i, l + 1 + n, c);
				addedge(l + 1 + n, i, c);
			}
		}
		int k = n;
		n *= 2;
		for (i = 0; i < m; i++)
		{
			scanf("%d%d%d", &x, &y, &z);
			addedge(x, y, z);
			addedge(y, x, z);
		}
		int ans = DIJKSTRA(1, k);
		printf("Case #%d: %d\n", kase++, ans);
	}
	//system("pause");
	return 0;
}

4:HDU 4185 Oil Skimming (二维 + 最大匹配)

原题链接

  • 题意:
    给一块正方形的地,分别有 .# ,问最多能组合多少个 ## 图形。
  • 分析:
    一开始看题时也知道是二分图最大匹配,所以什么都没想就直接在图上跑 dfs 去匹配, 然后 WA 了好几次。其实是因为在图上直接跑根本不好记录匹配对,所以正确的做法还是先建图,把二维变一维,转化成最普通的二分图来做。
  • 代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<string>
#include<cstdio>
#include<stack>
#include<cstring>
#include<utility>
#include<climits>
#pragma warning(disable : 4996)
using namespace std;

int n, m[610][610], sum, vis[360010], match[360010];
vector<int> vec[360010];
char s[610][610];
int w[4][2] = { {1,0},{-1,0},{0,1},{0,-1} };

int dfs(int x) {
	int i, len;
	len = vec[x].size();
	for (i = 0; i < len; i++)
	{
		int v = vec[x][i];
		if (vis[v])
			continue;
		vis[v] = 1;
		if (match[v] == -1 || dfs(match[v]))
		{
			match[v] = x;
			return 1;
		}
	}
	return 0;
}

int main() {
	int t, i, j, kase, res, k;
	kase = 1;
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d", &n);
		for (i = 0; i < n; i++)
			scanf("%s", s[i]);
		sum = 0;
		for (i = 0; i < n; i++)
			for (j = 0; j < n; j++)
			{
				if (s[i][j] == '#')
					m[i][j] = sum++;
			}
		for (i = 0; i < n; i++)
			for (j = 0; j < n; j++)
			{
				if (s[i][j] == '#')
				{
					for (k = 0; k < 4; k++)
					{
						int newx = i + w[k][0];
						int newy = j + w[k][1];
						if (newx < 0 || newx >= n || newy < 0 || newy >= n)
							continue;
						if (s[newx][newy] == '#')
						{
							int u = m[i][j];
							int v = m[newx][newy];
							vec[u].push_back(v);
							vec[v].push_back(u);
						}
					}
				}
			}
		memset(match, -1, sizeof(match));
		res = 0;
		for (i = 0; i < sum; i++)
		{
			memset(vis, 0, sizeof(vis));
			if (dfs(i))
				res++;
		}
		printf("Case %d: %d\n", kase++, res / 2);
		for (i = 0; i < sum; i++)
			vec[i].clear();
	}
	system("pause");
	return 0;
}


5:HDU 1281 棋盘游戏 (二维 + 最大匹配)

原题链接

  • 题意:
    一个棋盘只能在指定位置上放车,要求车不能相互攻击,记下最多能放下多少个车,如果去掉一个格子后,最大数量减少,则称该格子为重要格子,问有多少个重要格子。
  • 分析:
    同样是二维平面上的最大匹配问题,我们考虑在一个格子上放下格子,那么这一行和列都不能放了,这样该行与该列就匹配到一起了。因此把可以放下车的格子标记起来,然后先跑一遍最大匹配。那么该如何判断是否是重要格子呢?我们考虑将该格子的标记去掉,再跑一遍最大匹配,如果匹配数量减少了,那么该格子是重要格子,把数量记录下来。
  • 代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<cmath>
#include<iomanip>
#include<cstring>
#include<map>
#include<climits>
#include<cstdio>
#include<string>
#pragma warning(disable : 4996)
using namespace std;

int n, m, k, g[110][110], match[110], sum, vis[110][110];
int dfs(int u) {
	int i;
	for (i = 1; i <= m; i++)
	{
		if (vis[u][i] == 1)
			continue;
		if (g[u][i] == 0)
			continue;
		vis[u][i] = 1;
		if (match[i] == 0 || dfs(match[i]))
		{
			match[i] = u;
			return 1;
		}
	}
	return 0;
}

int main() {
	int i, x, y, j, count, res, kase;
	kase = 1;
	while (scanf("%d%d%d", &n, &m, &k) != EOF)
	{
		memset(g, 0, sizeof(g));
		for (i = 0; i < k; i++)
		{
			scanf("%d%d", &x, &y);
			g[x][y] = 1;
		}
		sum = 0;
		memset(match, 0, sizeof(match));
		for (i = 1; i <= n; i++)
		{
			memset(vis, 0, sizeof(vis));
			if (dfs(i))
				sum++;
		}
		count = 0;
		for (i = 1; i <= n; i++)
		{
			for (j = 1; j <= m; j++)
			{
				if (g[i][j] == 1)
				{
					g[i][j] = 0;
					res = 0;
					memset(match, 0, sizeof(match));
					for (int p = 1; p <= n; p++)
					{
						memset(vis, 0, sizeof(vis));
						if (dfs(p))
							res++;
					}
					if (res < sum)
						count++;
					g[i][j] = 1;
				}
			}
		}
		printf("Board %d have %d important blanks for %d chessmen.\n", kase++, count, sum);
	}
	system("pause");
	return 0;
}

6:CodeForces - 1343E Weights Distributing (思维 + 最短路)

原题链接

  • 题意:
    n座城市,m条路,m个权值,要求从 a 走到 b,再走到 c。问如何安排路的权值,使走的路权值总和最小。
  • 分析:
    一开始的分析是既然要求权值最小,那么先用 Dijkstra 跑最短路,然后把 babc 的路径记录下来,然后统计路径出现的次数,次数多的优先安排权值小的。
    这样忽略了一个问题,虽然单方面看是最短路,但是在统计合并的时候,由于边可能存在重叠,可能存在比上述情况更优的解法。
    因此要先求 a,b,c 点到各点的最短距离。然后构造路径 a-x,x-b,b-x,x-c,这样 x-b 这条路径要计算两次,然后枚举x点,找最小值。
  • 代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<string>
#include<cstdio>
#include<stack>
#include<cstring>
#include<utility>
#include<climits>
#pragma warning(disable : 4996)
using namespace std;

typedef long long ll;
const int N = 200010;
const int INF = 0x7f7f7f7f;
int f[N], da[N], db[N], dc[N];
int n, m, a, b, c;
ll pre[N];
vector<int> vec[N];

void bfs(int x, int d[]) {
	queue<int> que;
	int i, len;
	que.push(x);
	d[x] = 0;
	while (!que.empty())
	{
		int u = que.front();
		que.pop();
		len = vec[u].size();
		for (i = 0; i < len; i++)
		{
			int v = vec[u][i];
			if (d[v] == INF)
			{
				d[v] = d[u] + 1;
				que.push(v);
			}
		}
	}
}

int main() {
	int t, i, x, y;
	ll ans;
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d%d%d%d%d", &n, &m, &a, &b, &c);
		for (i = 0; i <= n; i++)
			vec[i].clear();
		for (i = 1; i <= m; i++)
			scanf("%d", &f[i]);
		sort(f + 1, f + 1 + m);
		for (i = 1; i <= m; i++)
			pre[i] = pre[i - 1] + f[i];
		for (i = 1; i <= m; i++)
		{
			scanf("%d%d", &x, &y);
			vec[x].push_back(y);
			vec[y].push_back(x);
		}
		memset(da, INF, sizeof(da));
		bfs(a, da);
		memset(db, INF, sizeof(db));
		bfs(b, db);
		memset(dc, INF, sizeof(dc));
		bfs(c, dc);
		ans = 1e18;
		for (i = 1; i <= n; i++)
		{
			if (da[i] + db[i] + dc[i] > m)
				continue;
			ans = min(ans, (ll)pre[db[i]] + (ll)pre[da[i] + db[i] + dc[i]]);
		}
		printf("%lld\n", ans);
	}
	system("pause");
	return 0;
}

7:稍难的数学 (数位dp)

题目描述
从阿拉伯数字1写到n一共有多少笔画

  • 分析:
    0到9之间只有4,5是两笔画,其他都是1笔画。问题可以先分解成数字x在1到n中一共出现了多少次。然后找规律:1-10中个位上每个数字都出现了1次;1-100中十位上每个数字都出现了10次;1-1000中百位上每个数字都出现了100次。
    按照此规律,当我们判断数字x中在1-n出现多少次时,可以一位一位地判断。比如计算5在1-2593中出现多少次。判断 个位 ,有259个10,因此出现259次,剩下1-3都比5小,所以不用计算。判断 十位,有25个100,因此出现25 * 10=250次,剩下93,9>5,因此加上10。判断 百位 ,有2个1000,因此出现2 * 100 = 200次,剩下593,百位上刚好是5,因此要加上93 + 1 = 94次。最后千位2<5,不用计算。
    另外数字0需要单独拿出来讨论,比如还是2593,计算到十位时,25个100,其中第一个100,包含了00,01,02 … 09,所以要对应减上10。 讨论百位时,也相应减上100。最后不需要讨论最高位。
    参考自blog
  • 代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<string>
#include<cstdio>
#include<stack>
#include<cstring>
#include<utility>
#include<climits>
#pragma warning(disable : 4996)
using namespace std;

typedef long long ll;

ll solve(ll n, int x) {
	ll i, k, cnt;
	cnt = 0;
	if (x == 0)
	{
		for (i = 1; ; i *= 10)
		{
			k = n / i;
			if (k / 10 == 0)
				break;
			cnt += (k / 10)*i;
			int cur = k % 10;
			if (cur == 0)
				cnt += n - k * i + 1 - i;
		}
		return cnt;
	}
	for (i = 1; ; i *= 10)
	{
		k = n / i;
		if (k == 0)
			break;
		cnt += (k / 10)*i;
		int cur = k % 10;
		if (cur > x)
			cnt += i;
		else if (cur == x)
			cnt += n - k * i + 1;
	}
	return cnt;
}

int main() {
	ll n, cnt;
	int i;
	scanf("%lld", &n);
	cnt = 0;
	for (i = 0; i <= 9; i++)
	{
		if (i == 4 || i == 5)
			cnt += 2 * solve(n, i);
		else
			cnt += solve(n, i);
	}
	printf("%lld\n", cnt);
	system("pause");
	return 0;
}

8:POJ 3104 Drying ( 二分:最大化最小值 )

原题链接

  • 题意:有 n 件衣服,每件衣服有 ai 湿度,每分钟会减少1湿度,而有机器可以使一件衣服每分钟减少 k 湿度,问让所有衣服都变干,最少需要多少分钟。
  • 分析:
    本题衣服晾干有两种方式,不妨设自然晾干x分钟,机器晾干y分钟。
    那么有:x + y * k >= aix + y <= ans
    综上得:y * ( k - 1 ) >= ai - ans
    用二分法模拟晾干过程,看结果与 mid 大小关系,如果实际需要的时间小于 mid,那么这个可能是答案,把它保存起来,继续下一次二分。
  • 代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<string>
#include<cstdio>
#include<stack>
#include<cstring>
#include<utility>
#include<climits>
#pragma warning(disable : 4996)
using namespace std;

typedef long long ll;
ll a[100010];

int main() {
	ll n, i, j, l, r, mid, cnt, k;
	scanf("%lld", &n);
	l = 0;
	r = 0;
	for (i = 0; i < n; i++)
	{
		scanf("%lld", &a[i]);
		r = max(r, a[i]);
	}
	scanf("%lld", &k);
	if (k == 1)
	{
		printf("%lld\n", r);
		return 0;
	}
	ll ans = r;
	while (r >= l)
	{
		cnt = 0;
		mid = (l + r) / 2;
		for (i = 0; i < n; i++)
		{
			if (a[i] > mid)
			{
				if ((a[i] - mid) % (k - 1) == 0)
					cnt += (a[i] - mid) / (k - 1);
				else
					cnt += (a[i] - mid) / (k - 1) + 1;
			}
		}
		if (cnt > mid)
			l = mid + 1;
		else
		{
			ans = min(ans, mid);
			r = mid - 1;
		}
	}
	printf("%lld\n", ans);
	//system("pause");
	return 0;
}

9:Nastya and Scoreboard ( 预处理 + dp + 贪心 )

原题链接

  • 题意:
    一个数字位上有7个灯管,他们点亮的组合方式代表不同数字,问有 n 个数字位,需要点亮 k 个灯管,使数字最大的结果是多少?如果不可能,那么输出-1。
  • 分析:
    这题初步的想法是贪心,尽量使高位数字更大,然后往后递推。往后推的过程中可能会出现点亮数用不完的情况,然后又要回溯找到上一步的可行方案,继续重新推。因此这一题需要用 dp 来记录状态。但是每一次推的过程中,重新去计算每一位点亮灯管的数量是很浪费时间的,所以要先预处理出各个数字位变成 0-9 所需要点亮的灯管数。
    关键是确定动态规划中的状态了,用 dp [ i ] [ j ] 来表示第 i 位剩 j 个点亮灯管的最大数,这显然不太现实,因为这牵涉到了位数的计算。相反,我们只需要表示这个方案可不可行就足够了,从第 n 位往前推,初始状态 dp[ n + 1 ] [ 0 ] 设为 1
    最后利用贪心策略,根据前面 dp 得到的结果把答案记录下来即可,如果 dp [ 1 ] [ k ] == 0 ,表示没有方案可行,输出-1。
  • 代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<string>
#include<cstdio>
#include<stack>
#include<cstring>
#include<utility>
#include<climits>
#pragma warning(disable : 4996)
using namespace std;

int n, k;
char num[10][10] = { "1110111", "0010010", "1011101", 
		"1011011", "0111010", "1101011", "1101111", "1010010", "1111111", "1111011" };
int dp[2010][2010], cost[2010][2010];
char a[2010][10], ans[2010];

void calCost() {
	int i, j, p;
	memset(cost, 0, sizeof(cost));
	for (i = 1; i <= n; i++)
	{
		for (j = 0; j <= 9; j++)
		{
			for (p = 0; p < 7; p++)
			{
				if (a[i][p] == '0'&&num[j][p] == '1')
					cost[i][j]++;
				else if (a[i][p] == '1'&&num[j][p] == '0')
				{
					cost[i][j] = -1;
					break;
				}
			}
		}
	}
}

void DP() {
	int i, j, p;
	dp[n + 1][0] = 1;
	for (i = n; i > 0; i--)
	{
		for (j = 0; j <= 9; j++)
		{
			if (cost[i][j] == -1)
				continue;
			for (p = cost[i][j]; p <= k; p++)
			{
				if (dp[i + 1][p - cost[i][j]])
					dp[i][p] = 1;
			}
		}
	}
}

void greedy() {
	int i, j;
	for (i = 1; i <= n; i++)
	{
		for (j = 9; j >= 0; j--)
		{
			if (cost[i][j] == -1)
				continue;
			if (k >= cost[i][j] && dp[i + 1][k - cost[i][j]] == 1)
			{
				k -= cost[i][j];
				ans[i] = j + '0';
				break;
			}
		}
	}
}

int main() {
	int i, j, flag, m;
	scanf("%d%d", &n, &k);
	m = k;
	for (i = 1; i <= n; i++)
		scanf("%s", a[i]);
	calCost();
	DP();
	greedy();
	flag = 0;
	if (dp[1][m] == 0)
		printf("-1\n");
	else
	{
		ans[n + 1] = '\0';
		printf("%s\n", ans + 1);
	}
	system("pause");
	return 0;
}

10:Priest John’s Busiest Day( 2 - SAT )

原题链接

  • 题意:
    n 场仪式,给出开始时间,结束时间和仪式持续时间,选择要么从开始时间举行,要么在结束时间前举行,问能否合理安排各场仪式时间。
  • 分析:
    一场仪式有两种选择,转化为原命题和否命题,用 2 - SAT 解决。因为 n 最大为1000,所以 i 代表原命题, i + 1000 代表否命题。命题是否冲突处理上,先把时间转化为分钟,然后 ( t1 , t2 ) ( t3 , t4 ) 两个时间段如果存在 min ( t2 , t4 ) > max ( t1 , t3 ) ,则冲突,建边。
  • 分析:
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<string>
#include<cstdio>
#include<stack>
#include<cstring>
#include<utility>
#include<climits>
#pragma warning(disable : 4996)
using namespace std;

int n, m, vis[2010], cmp[2010];
vector<int> vec[2010], rev[2010];
vector<int> st;

struct node {
	int s, e, t1, t2;
	int mark;
}nodes[1010];

void dfs(int x) {
	vis[x] = 1;
	int i, len;
	len = vec[x].size();
	for (i = 0; i < len; i++)
	{
		if (vis[vec[x][i]] == 0)
			dfs(vec[x][i]);
	}
	st.push_back(x);
}

void rdfs(int x, int k) {
	vis[x] = 1;
	cmp[x] = k;
	int i, len;
	len = rev[x].size();
	for (i = 0; i < len; i++)
	{
		if (vis[rev[x][i]] == 0)
			rdfs(rev[x][i], k);
	}
}

void addedge(int a, int b) {
	vec[(a + 1000) % 2000].push_back(b);
	vec[(b + 1000) % 2000].push_back(a);
	rev[b].push_back((a + 1000) % 2000);
	rev[a].push_back((b + 1000) % 2000);
}

int main() {
	int i, a, b, x, y, j, time;
	int t1, t2;
	scanf("%d", &n);
	for (i = 0; i < n; i++)
	{
		scanf("%d:%d %d:%d %d", &a, &b, &x, &y, &time);
		nodes[i].s = a * 60 + b;
		nodes[i].e = x * 60 + y;
		nodes[i].t1 = nodes[i].s + time;
		nodes[i].t2 = nodes[i].e - time;
		for (j = 0; j < i; j++)
		{
			if (min(nodes[i].t1, nodes[j].t1) > max(nodes[i].s, nodes[j].s))   //(s, t1) (s, t1)
				addedge(i, j);
			if (min(nodes[i].t1, nodes[j].e) > max(nodes[i].s, nodes[j].t2))   //(s, t1) (t2, e)
				addedge(i, j + 1000);
			if (min(nodes[i].e, nodes[j].t1) > max(nodes[i].t2, nodes[j].s))   //(t2, e)  (s, t1)
				addedge(i + 1000, j);
			if (min(nodes[i].e, nodes[j].e) > max(nodes[i].t2, nodes[j].t2))   //(t2, e)  (t2, e)
				addedge(i + 1000, j + 1000);
		}
	}
	memset(vis, 0, sizeof(vis));
	for (i = 0; i < n; i++)
	{
		if (vis[i] == 0)
			dfs(i);
	}
	for (i = 1000; i < 1000 + n; i++)
	{
		if (vis[i] == 0)
			dfs(i);
	}
	int len = st.size();
	memset(vis, 0, sizeof(vis));
	int k = 1;
	for (i = len - 1; i >= 0; i--)
	{
		if (vis[st[i]] == 0)
		{
			rdfs(st[i], k);
			k++;
		}
	}
	int flag = 1;
	for (i = 0; i < n; i++)
	{
		//cout << cmp[i] << " " << cmp[i + 1000] << endl;
		if (cmp[i] < cmp[i + 1000])
			nodes[i].mark = 1;
		else if(cmp[i] > cmp[i + 1000])
			nodes[i].mark = 0;
		else
		{
			flag = 0;
			break;
		}
	}
	if (flag)
		printf("YES\n");
	else
	{
		printf("NO\n");
		system("pause");
		return 0;
	}
	for (i = 0; i < n; i++)
	{
		if (nodes[i].mark == 1)
		{
			a = nodes[i].s / 60;
			b = nodes[i].s % 60;
			x = nodes[i].t1 / 60;
			y = nodes[i].t1 % 60;
		}
		else
		{
			a = nodes[i].t2 / 60;
			b = nodes[i].t2 % 60;
			x = nodes[i].e / 60;
			y = nodes[i].e % 60;
		}
		printf("%02d:%02d %02d:%02d\n", a, b, x, y);
	}
	system("pause");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值