写在前面的话:我也是初学,有些分析或知识会有错误,望各位大佬们指教
目录
- 1:AOJ 2249 Road Construction (Dijkstra)
- 2:AOJ 2200 Mr. Rito Post Office(动态规划 + Floyd + 暴力枚举)
- 3:HDU 4725 The Shortest Path in Nya Graph(Dijkstra + 分层建图 + 堆优化 )
- 4:HDU 4185 Oil Skimming (二维 + 最大匹配)
- 5:HDU 1281 棋盘游戏 (二维 + 最大匹配)
- 6:CodeForces - 1343E Weights Distributing (思维 + 最短路)
- 7:稍难的数学 (数位dp)
- 8:POJ 3104 Drying ( 二分:最大化最小值 )
- 9:Nastya and Scoreboard ( 预处理 + dp + 贪心 )
- 10:Priest John's Busiest Day( 2 - SAT )
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 跑最短路,然后把 b 到 a 和 b 到 c 的路径记录下来,然后统计路径出现的次数,次数多的优先安排权值小的。
这样忽略了一个问题,虽然单方面看是最短路,但是在统计合并的时候,由于边可能存在重叠,可能存在比上述情况更优的解法。
因此要先求 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 >= ai 和 x + 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;
}