A Multiplication Puzzle
[Solution]
区间dp水题
[Code]
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 1000 + 5;
#define inf 0x3f3f3f3f
vector<int > s[N];
int f[N][N];
int n, a[N];
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
f[i][j] = inf;
for(int i = 1; i <= n; i++)
{
f[i][i] = 0;
f[i][i + 1] = 0;
}
for(int p = 2; p < n; p++)
for(int i = 1; p + i <= n; i++)
{
int j = i + p;
for(int k = i + 1; k < j; k++)
f[i][j] = min(f[i][j], f[i][k] + f[k][j] + a[i] * a[j] * a[k]);
}
printf("%d", f[1][n]);
return 0;
}
B - Coloring Brackets
[Problem]
给定一个合法的括号序列,现在我们给括号染色,可以染成红色和蓝色,询问最终方案数。
[Solution]
使用f[i][j][l][r]代表(i,j)这个区间左端l状态,右端j状态的方案数,这样分i、j匹配和不匹配来分类讨论,注意为了简化编码复杂度,我们可以可以将不合法的状态方案数标记为0,这样累加的时候判断相对容易
[Code]
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
const int N = 700 + 50;
const int mo = 1e9 + 7;
#define inf 0x3f3f3f3f
string ss;
int n, m, k, Case = 0, l[N];
ll f[N][N][3][3];
int main()
{
// freopen("b.in", "r", stdin);
cin>>ss;
n = ss.length();
stack<int> s;
for(int i = 1; i <= n; i++)
if (ss[i - 1] == '(')
s.push(i);
else
{
int x = s.top();
l[x] = i;
s.pop();
}
for(int i = 1; i < n; i++)
if (ss[i - 1] == '(' && ss[i] == ')')
{
f[i][i + 1][0][1] = 1;
f[i][i + 1][1][0] = 1;
f[i][i + 1][0][2] = 1;
f[i][i + 1][2][0] = 1;
}
for(int p = 3; p < n; p++)
if (p % 2 == 1)
for(int i = 1; i + p <= n; i++)
{
int j = i + p;
if (l[i] == j)
{
for(int ll = 0; ll < 3; ll++)
for(int rr = 0; rr < 3; rr++)
{
if (rr != 1)
f[i][j][0][1] = (f[i][j][0][1] + f[i + 1][j - 1][ll][rr]) % mo;
if (ll != 1)
f[i][j][1][0] = (f[i][j][1][0] + f[i + 1][j - 1][ll][rr]) % mo;
if (ll != 2)
f[i][j][2][0] = (f[i][j][2][0] + f[i + 1][j - 1][ll][rr]) % mo;
if (rr != 2)
f[i][j][0][2] = (f[i][j][0][2] + f[i + 1][j - 1][ll][rr]) % mo;
}
}
else
{
int x = l[i];
// printf("%d %d %d %d\n", i, x, x + 1, j);
for(int l1 = 0; l1 < 3; l1++)
for(int r1 = 0; r1 < 3; r1++)
for(int r2 = 0; r2 < 3; r2++)
for(int l2 = 0; l2 < 3; l2++)
if (!((l2 == 1 && r1 == 1) || (l2 == 2 && r1 == 2)) )
{
f[i][j][l1][r2] = (f[i][j][l1][r2] + f[i][x][l1][r1] * f[x + 1][j][l2][r2]) % mo;
// printf("(%d %d %d %d)- %d %d\n", l1, r1, l2, r2, f[i][x][l1][r1], f[x + 1][j][l2][r2]);
}
}
}
ll ans = 0;
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
ans = (ans + f[1][n][i][j]) % mo;
cout<<ans;
return 0;
}
C - Halloween Costumes
[Solution]
如果从状态上去考虑此道题目的会gg,因为对于每一天,身上穿的衣服有好多种情况,是不能存储的
我们从每天的决策去考虑,可以选择穿一件新衣服,或者脱掉一些衣服,即我们假设现在是第i天,需要用第k天的衣服,这样(k + 1, j-1)这些天就不能用前i天的衣服了,这样我们用f[i][j]代表(i,j)这些天独立的最优解
f[i][j] = f[i][k] + f[k + 1][j - 1]
[Code]
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
const int N = 1000 + 50;
const int mo = 1e9 + 7;
#define inf 0x3f3f3f3f
vector<int > s[N];
typedef long long ll;
int n, m, k, f[N][N], a[N], Case = 0;
int main()
{
// freopen("b.in", "r", stdin);
int T;
scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
f[i][j] = inf;
for(int i = 1; i < n; i++)
{
f[i][i] = 1;
if (a[i] == a[i + 1])
f[i][i + 1] = 1;
else
f[i][i + 1] = 2;
}
f[n][n] = 1;
for(int p = 2; p < n; p++)
for(int i = 1; i + p <= n; i++)
{
int j = i + p;
int t = 1;
if (a[j] == a[j - 1])
t = 0;
f[i][j] = min(f[i][j], f[i][j - 1] + t);
for(int k = i; k + 1 <= j - 1; k++)
if (a[k] == a[j])
f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j - 1]);
}
printf("Case %d: %d\n", ++Case, f[1][n]);
}
return 0;
}
D - Food Delivery
[Problem]
坐标轴上有n个点,初始值自己位于一个点,你需要经过每个点一次,每个点有个愤怒值,会随着时间的增加而线性增加,询问总愤怒值最小化的方案。
[Solution]
本题很容易想到部分贪心策略,即在初始点的同一侧的两个点x, y, 离初始点较近的点肯定优先的到满足,因此对于满足[l, r]区间后,肯定位于l位置或r位置,因此我们用f[i][j]代表处理区间(i, j)后,位于i位置,g[i][j]则代表位于j位置的最优解,但是[i][j]区间的最小代价的转移需要到现在位置花费的时间,而我们每次取最小代价维护的最小时间是不对的,因为可能此次尽可能地让时间小一些,虽然此次决策的代价稍大一些,但是下一步的决策要优很多
我们从另一个方向上去考虑吧,我们每次转移的时候不能同时保证两个状态最优,因此我们累加答案可以换一个姿势,先前我们通过点的方式来累加答案,但是时间这个变量难以维护,这下我们通过时间的方式,即此次转移,[i][j]区间之外的点都会增加愤怒值,这样转移就不会出现两个最优性方案了
[Code]
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 1000 + 5;
#define inf 0x3f3f3f3f
vector<int > s[N];
ll f[N][N], g[N][N];
ll sum[N];
struct node{
int x, y;
};
node point[N];
int n, v, st, res;
bool cmp(node a, node b)
{
return a.x < b.x;
}
int main()
{
// freopen("b.in", "r", stdin);
while(~scanf("%d%d%d", &n, &v, &st))
{
for(int i = 1; i <= n ;i++)
scanf("%d%d", &point[i].x, &point[i].y);
n++;
point[n].x = st;
point[n].y = 0;
sort(point + 1, point + n + 1, cmp);
for(int i = 1; i <= n; i++)
if (point[i].x == st)
{
res = i;
break;
}
sum[0] = 0LL;
for(int i = 1; i <= n; i++)
sum[i] = sum[i - 1] + point[i].y;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
{
f[i][j] = inf;
g[i][j] = inf;
}
f[res][res] = 0;
g[res][res] = 0;
for(int i = res; i >= 1; i--)
for(int j = res; j <= n; j++)
{
if (i == j && i == res)
continue;
f[i][j] = min(f[i][j], f[i + 1][j] + 1LL * abs(point[i + 1].x - point[i].x) * (sum[n] - sum[j] + sum[i]));
f[i][j] = min(f[i][j], g[i + 1][j] + 1LL * abs(point[j].x - point[i].x) * (sum[n] - sum[j] + sum[i]));
g[i][j] = min(g[i][j], g[i][j - 1] + 1LL * abs(point[j].x - point[j - 1].x) * (sum[n] - sum[j - 1] + sum[i - 1]));
g[i][j] = min(g[i][j], f[i][j - 1] + 1LL * abs(point[j].x - point[i].x) * (sum[n] - sum[j - 1] + sum[i - 1]));
// printf("f[%d][%d] = %d\n", i, j, f[i][j]);
// printf("g[%d][%d] = %d\n", i, j, g[i][j]);
}
cout<<min(f[1][n], g[1][n]) * v<<endl;
}
return 0;
}
F - Asteroids
[Problem]
N * N 的网格中有k个路障,每次可以清除一列或一行的路障,询问最小次数(n < 500)
[Solution]
对于二分图,有最大匹配数等于最小点覆盖数,即我们选取选取尽可能少的点,使得每个边都有点覆盖,选取的最小点数即最小点覆盖数。
这样我们要清除掉所有的路障,因此我们把路障类比为边, 那什么类比成点呢,每条边连接的东西便是点,而路障连接的不是点数和列数吗?
因此我们把每一行的编号类比成点,每一行的列类比成二分图的另外一部分的点,这样是一个二分图,把路障类比成边,这样跑一遍匈牙利算法算出最小点覆盖数即可
[Code]
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int N = 505;
vector<int > s[N];
int girl[N];
bool used[N];
int n, m, k;
bool found(int x)
{
for(int i = 0; i < s[x].size(); i++)
{
int y = s[x][i];
if (used[y]) continue;
used[y] = true;
if (girl[y] == 0 || found(girl[y]))
{
girl[y] = x;
return 1;
}
}
return 0;
}
int main()
{
// freopen("b.in", "r", stdin);
while(~scanf("%d%d", &n, &k))
{
memset(girl, 0, sizeof(girl));
for(int i = 1; i <= n; i++)
s[i].clear();
for(int i = 1; i <= k; i++)
{
int x, y;
scanf("%d%d", &x, &y);
s[x].push_back(y);
}
int ans = 0;
for(int i = 1; i <= n; i++)
{
memset(used, 0, sizeof(used));
if (found(i))
ans++;
}
printf("%d\n", ans);
}
return 0;
}
G - Chessboard
[Problem]
N* M的网格中有一些障碍物,现有1*2格纸若干,判断是否将格纸覆盖住网格,使得每一个网格上只有一层贴纸。
[Solution]
十分经典的二分图匹配,首先,同一个贴纸的两个点行数与列数和的奇偶性肯定相反,这样我们把行列数为奇数的为一类点,行列数为偶数的为一类点,这样把一个点与其四周的点,连边,最后跑一边二分图最大匹配即可
[Code]
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
const int N = 1000 + 50;
const int mo = 1e9 + 7;
#define inf 0x3f3f3f3f
vector<int > s[N];
typedef long long ll;
int n, m, k, a[N][N], girl[N];
bool used[N];
int dx[] = {1, 0, -1, 0};
int dy[] = {0, 1, 0, -1};
bool found(int x)
{
for(int i = 0; i < s[x].size(); i++)
{
int y = s[x][i];
if (used[y]) continue;
used[y] = true;
if (girl[y] == 0 || found(girl[y]))
{
girl[y] = x;
return 1;
}
}
return 0;
}
int main()
{
// freopen("b.in", "r", stdin);
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
int top1 = 0, top2 = 0;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
if ((i + j) % 2 == 0)
a[i][j] = ++top1;
else
a[i][j] = ++top2;
if ((n * m - k) % 2 == 1)
{
printf("NO");
return 0;
}
for(int i = 1; i <= k; i++)
{
int x, y;
scanf("%d%d", &y, &x);
a[x][y] = -1;
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
if (a[i][j] > 0 && (i + j) % 2 == 0)
{
int x = a[i][j];
for(int v = 0; v < 4; v++)
{
int xx = i + dx[v];
int yy = j + dy[v];
if (xx <= 0 || xx > n || yy <= 0 || yy > m)
continue;
if (a[xx][yy] <= 0)
continue;
int y = a[xx][yy];
s[x].push_back(y);
// printf("---%d %d\n",x , y);
}
}
int ans = 0;
for(int i = 1; i <= top1; i++)
{
memset(used, 0, sizeof(used));
if (found(i))
{
ans++;
//printf("%d\n", i);
}
}
// printf("%d\n", ans);
int x = n * m - k;
printf("%s", x == 2 * ans ? "YES" : "NO");
return 0;
}
H - Book Club
[Problem]
n个人有n本书,每个人都有自己喜欢的书,判断是否存在一种交换方案,使得每个人获得一本自己喜爱的书。
[Solution]
很明显的二分图最大匹配
[Code]
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int N = 10000 + 5;
vector<int > s[N];
int girl[N];
bool used[N];
int n, m, k;
bool found(int x)
{
for(int i = 0; i < s[x].size(); i++)
{
int y = s[x][i];
if (used[y]) continue;
used[y] = true;
if (girl[y] == 0 || found(girl[y]))
{
girl[y] = x;
return 1;
}
}
return 0;
}
int main()
{
// freopen("b.in", "r", stdin);
while(~scanf("%d%d", &n, &k))
{
memset(girl, 0, sizeof(girl));
for(int i = 1; i <= n; i++)
s[i].clear();
for(int i = 1; i <= k; i++)
{
int x, y;
scanf("%d%d", &x, &y);
x++;
y++;
s[x].push_back(y);
}
int ans = 0;
for(int i = 1; i <= n; i++)
{
memset(used, 0, sizeof(used));
if (found(i))
{
ans++;
// printf("%d\n", i);
}
}
if (ans == n)
printf("YES");
else
printf("NO");
}
return 0;
}