Problem A 物资调度
题目链接:http://nyoj.top/problem/1249
题意:求方案总数,有n个数,每个数的个数已知,求能拼凑出m的方案数。
思路:类似与01背包,每一种状态都是由上一种状态继承来的,dp数组存贮能够组合当前数的总个数,则状态转移方程为:dp[j]+=dp[j-a[i]].另外一种方法就是用DFS搜。
DP:
#include <bits/stdc++.h>
using namespace std;
int val[105], dp[1005];
int main() {
int t, n, m;
scanf("%d", &t);
while (t--) {
memset(dp, 0, sizeof(dp));
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++)
scanf("%d", &val[i]);
dp[0] = 1;
for (int i = 0; i < n; i++) {
for (int j = m; j >= val[i]; j--)
dp[j] += dp[j - val[i]];
}
printf("%d\n", dp[m]);
}
return 0;
}
DFS:
#include <bits/stdc++.h>
using namespace std;
int sum, ans, n, m, v[105], vis[105];
void DFS(int cnt, int k) {
if (cnt >= m) {
if (cnt == m)
ans++;
return ;
}
for (int i = k; i < n; i++) {
if (!vis[i]) {
vis[i] = 1;
DFS(cnt + v[i], i + 1);
vis[i] = 0;
}
}
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
ans = 0;
memset(vis, 0, sizeof(vis));
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) {
scanf("%d", &v[i]);
}
DFS(0, 0);
printf("%d\n", ans);
}
return 0;
}
Problem B 海岛争霸
题目链接:http://nyoj.top/problem/1248
题意:求A到B所经过的航线危险程度的最小值。
思路:最短路变形题,稍微修改一下状态转移方程就可以了。最短路是求和,这题是取最大。这道题其实也可以用并查集做,先把权值从小到大排一下序,然后就开始加边,直到A与B能连通。
最短路:
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
struct edge {
int u, v, w;
} e[1005];
int f[105], vis[105], dis[105], cnt = 0;
void Add(int u, int v, int w) {
e[++cnt] = (edge){f[u], v, w};
f[u] = cnt;
}
void Spfa(int s) {
queue <int> Q;
Q.push(s);
vis[s] = 1;
dis[s] = 0;
while (!Q.empty()) {
int u = Q.front();
Q.pop();
vis[u] = 0;
for (int i = f[u]; i; i = e[i].u) {
int v = e[i].v;
if (dis[v] > max(dis[u], e[i].w)) {
dis[v] = max(dis[u], e[i].w);
if (!vis[v]) {
Q.push(v);
vis[v] = 1;
}
}
}
}
}
int main() {
int n, m, u, v, w, q, A, B;
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i++) {
scanf("%d%d%d", &u, &v, &w);
Add(u, v, w);
Add(v, u, w);
}
scanf("%d", &q);
while (q--) {
for (int i = 0; i <= n; i++)
dis[i] = inf;
memset(vis, 0, sizeof(vis));
scanf("%d%d", &A, &B);
Spfa(A);
if (dis[B] < inf)
printf("%d\n", dis[B]);
else printf("-1\n");
}
return 0;
}
并查集:
#include <bits/stdc++.h>
using namespace std;
struct edge {
int u, v, w;
}e[505];
int f[105];
bool cmp(edge a, edge b) {
return a.w < b.w;
}
int getf(int v) {
if (f[v] != v)
return f[v] = getf(f[v]);
return v;
}
bool merge(int u, int v) {
int t1 = getf(u);
int t2 = getf(v);
if (t1 != t2) {
f[t2] = t1;
return true;
}
return false;
}
int main() {
int a, b, n, m, q, ans, max_;
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i++)
scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
sort(e, e + m, cmp);
scanf("%d", &q);
while (q--) {
max_ = -1;
scanf("%d%d", &a, &b);
for (int i = 0; i <= n; i++)
f[i] = i;
for (int i = 0; i < m && !~max_; i++) {
if (merge(e[i].u, e[i].v))
ans = e[i].w;
if (getf(a) == getf(b))
max_ = ans;
}
printf("%d\n", max_);
}
return 0;
}
Problem D 山区修路
题目链接:http://nyoj.top/problem/1251
题意:把一串序列变为一段连续不增,或者连续不减的最小花费。
思路:解题关键是要注意到不同的高度数并不多,无论使路段上升还是下降,总是可以调整到另一个已经存在的高度,使得结果最优。dp[i][j]代表第i个元素换为第j个值的前i个总的最小花费,可列出状态转移方程:
dp[i][j]=abs(a[i]-b[j])+ab,ab为转化为1~j间的最小花费;由于是单调不增或者单调不减,只需要升序降序下b数组就可以了.
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
int n, a[505], b[505], dp[505][505];
int solve() {
int min_;
memset(dp, 0, sizeof(dp));
for (int i = 0; i < n; i++) {
min_ = inf;
for (int j = 0; j < n; j++) {
min_ = min(min_, dp[i][j]);
dp[i + 1][j] = abs(b[j] - a[i]) + min_;
}
}
min_ = inf;
for (int i = 0; i < n; i++)
min_ = min(min_, dp[n][i]);
return min_;
}
int main() {
int t, min_;
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
b[i] = a[i];
}
sort(b, b + n);
min_ = solve();
reverse(b, b + n);
min_ = min(min_, solve());
printf("%d\n", min_);
}
return 0;
}
Problem E 世界之威
题目链接:http://nyoj.top/problem/1252
题意:n个武器,希望被卖出的武器中都至少有一种限制它的武器在手中,求最多能卖出去多少武器。
思路:图的遍历。首先是建图,如果u克制v,就连一条有向边uv。因为一种武器只能克制一种武器,所以图的结构可能出现链和环,还有可能多条边汇聚在一个点,不可能出现一个点引出多条边。首先我们找到所有入度为0的点,这些武器肯定是不能卖的,因为无法克制它们。如果u入度为0,我们就从u开始遍历,一定能得到一条链,这条链的贡献就是链长度/2。把所有入度为0的点处理完后,所有的链就处理完了,剩下的就是环。从每个环的任意一个点开始遍历,得到环的长度,这个环的贡献就是环长度/2。
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
int deg[1000005], pre[1000005], vis[1000005];
int main() {
int t, n, x, ans;
scanf("%d", &t);
while (t--) {
ans = 0;
scanf("%d", &n);
memset(deg, 0, sizeof(deg));
memset(pre, 0, sizeof(pre));
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; i++) {
scanf("%d", &x);
pre[i] = x;
deg[x]++;
}
for (int i = 1; i <= n; i++) {
if (!deg[i]) {
int cnt = 0, a = i;
while (a && !vis[a]) {
vis[a] = 1;
a = pre[a];
cnt++;
}
ans += cnt >> 1;
}
}
for (int i = 1; i <= n; i++) {
if (!vis[i]) {
int cnt = 0, a = i;
while (!vis[a]) {
vis[a] = 1;
a = pre[a];
cnt++;
}
ans += cnt >> 1;
}
}
printf("%d\n", ans);
}
return 0;
}
Problem F Turing equation
题目链接:http://nyoj.top/problem/1253
题意:机器会将数倒着看,请判断输入的式子是否正确。
思路:把数翻一下,验证正确性就行了。
#include <bits/stdc++.h>
using namespace std;
char str[30];
int Judge(char *str, int l, int r) {
int ans = 0;
for (int i = l; i >= r; i--)
ans = ans * 10 + str[i] - '0';
return ans;
}
int main() {
int m, k, len, s[3];
while (~scanf("%s", str)) {
len = strlen(str);
if (len == 5 && str[0] + str[2] + str[4] == 3 * '0')
break;
k = m = 0;
for (int i = 0; i < len; i++) {
if (str[i] == '+' || str[i] == '=') {
s[k++] = Judge(str, i - 1, m);
m = i + 1;
}
}
s[k++] = Judge(str, len - 1, m);
if (s[0] + s[1] != s[2])
printf("FALSE\n");
else printf("TRUE\n");
}
return 0;
}
Problem G Code the Tree
题目链接:http://nyoj.top/problem/1254
题意:给你一个字符串表示一棵无根树。让你求出该树的purfer序列。
思路:模拟题。根据给的串建树,左括号进入下一层,右括号返回上一层。建树完成后,维护每个点的度,依次删除度为1的最小的点和它唯一的边,输出边连接的另一个点。注意树根也可以作为叶子,只要点的度为1,它就是叶子。
#include <bits/stdc++.h>
using namespace std;
int p, n, deg[55], mp[55][55];
int Create(char str[]) {
int num, wei, ans;
while (str[p]) {
if (isdigit(str[p])) {
sscanf(str + p, "%d%n", &num, &wei);
p += wei;
n++;
}
else if (str[p] == '(') {
p++;
ans = Create(str);
deg[num]++;
deg[ans]++;
mp[ans][num] = mp[num][ans] = 1;
}
else if (str[p] == ')') {
p++;
return num;
}
else p++;
}
}
int main() {
char str[250];
while (~scanf("%[^\n]", str)) {
getchar();
n = 0, p = 1;
memset(mp, 0, sizeof(mp));
memset(deg, 0, sizeof(deg));
Create(str);
for (int i = 0; i < n - 2; i++) {
for (int j = 1; j < n; j++) {
if (deg[j] == 1) {
deg[j] = 0;
for (int k = 1; k <= n; k++) {
if (mp[j][k]) {
deg[k]--;
printf("%d ", k);
mp[j][k] = mp[k][j] = 0;
break;
}
}
break;
}
}
}
printf("%d\n", n);
}
return 0;
}
Problem H Rectangles
题目链接:http://nyoj.top/problem/1255
题意:有n个矩阵,如果大的套小的看最多能套几层。大的矩阵必须有一边大于小矩阵的一边,例如2*2的矩阵可以套2*1的矩阵,但是不能套2*2的矩阵,注意矩形是可以旋转的。
思路:首先将矩阵长的边算作x边,小的算y边,然后将n个矩阵按照x的长短排序若x相同则按照y排序,然后跑一遍LIS就行了。
#include <bits/stdc++.h>
using namespace std;
struct edge {
int l, w;
}e[105];
int dp[105];
bool cmp(edge A, edge B) {
if (A.l != B.l)
return A.l < B.l;
return A.w < B.w;
}
bool Judge(int a, int b) {
if (e[a].l <= e[b].l && e[a].w < e[b].w)
return true;
if (e[a].l < e[b].l && e[a].w <= e[b].w)
return true;
return false;
}
int main() {
int t, n, max_, tempmax_;
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d%d", &e[i].l, &e[i].w);
if (e[i].l < e[i].w)
swap(e[i].l, e[i].w);
}
sort(e, e + n, cmp);
max_ = dp[0] = 1;
for (int i = 1; i < n; i++) {
tempmax_ = 0;
for (int j = 0; j < i; j++) {
if (Judge(j, i) && tempmax_ < dp[j])
tempmax_ = dp[j];
}
dp[i] = tempmax_ + 1;
max_ = max(max_, dp[i]);
}
printf("%d\n", max_);
}
return 0;
}