T1
Topcoder SRM 578 div1 Medium - WolfInZooDivOne
考虑转化限制。在一个序列中填若干 \(1\),对每个 \(1\) 的上一个 \(1\) 的上一个 \(1\) 的位置有限制。设某个 \(1\) 的上一个 \(1\) 的位置为 \(pre_i\),则如果一个限制为 \([l, r]\),则要求 \(\forall i \in [l, r], pre_{pre_i} < l\)。这样就可以 dp 了,状态为 \(dp[i][j]\),表示前 \(i\) 位,要求最后一个位置是 \(1\),而最后一个位置的前一个 \(1\) 位置在 \(j\) 的方案数。然后根据限制随便转移一下就可以了。
代码
#include <iostream>
#define int long long
using namespace std;
const int P = 1000000007;
void input(int &n,int *fa){
int T,x;
string s,t;
scanf("%lld\n", &T);
for(;T--;){
getline(cin,t);
s+=t;
}
s+=' ',n=0,x=0;
for(char c:s){
if(c==' ')fa[++n]=x,x=0;
else if(isdigit(c))x=x*10+c-'0';
}
}
int n;
int l[305], r[305];
int dp[305][305];
int lim[305];
int m;
signed main() {
cin >> n;
input(m, l);
input(m, r);
for (int i = 1; i <= n; i++) lim[i] = 2147483647, dp[i][0] = 1;
for (int i = 1; i <= m; i++) {
++l[i], ++r[i];
for (int j = l[i]; j <= r[i]; j++)
lim[j] = min(lim[j], l[i]);
}
int ans = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j < i; j++) {
for (int k = 0; k < j; k++) {
if (k < lim[i])
dp[i][j] += dp[j][k];
}
dp[i][j] %= P;
}
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++)
ans += dp[i][j];
}
cout << ans % P << "\n";
return 0;
}
T2
Topcoder SRM 569 div1 Medium - TheJediTest
直接贪心。从左往右考虑每一个位置,依次做一下操作:
- 如果左边还没满(不是 \(K\) 的倍数),就尽量把当前位置的值往左扔,直到左边满了或者当前位置无人可走为止。如果左边满了,我们不去动它。
- 接下来尝试把当前位置上多的东西往右扔。如果当前位的限制还足够把这一位剩的全扔到右边去,那就直接扔。否则就不扔了。
然后每次操作时维护一下每一位都有多少人以及每个位置上的限制就能过了。感性理解一下,发现确实是对的。
代码
#include <iostream>
#define int long long
using namespace std;
int a[25], b[25], c[25];
signed main() {
int n, k;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
cin >> k;
for (int i = 1; i <= n; i++) b[i] = a[i], c[i] = 0;
for (int i = 1; i <= n; i++) {
if (i != 1 && (b[i - 1] + c[i - 1]) % k) {
int tmp1 = b[i], tmp2 = k - (b[i - 1] + c[i - 1]) % k;
c[i - 1] += min(tmp1, tmp2), b[i] -= min(tmp1, tmp2);
}
if (i != n && b[i] >= (b[i] + c[i]) % k)
c[i + 1] += (b[i] + c[i]) % k, b[i] -= (b[i] + c[i]) % k;
}
int ans = 0;
for (int i = 1; i <= n; i++) ans += (b[i] + c[i] + k - 1) / k;
cout << ans << "\n";
return 0;
}
T3
Topcoder SRM 602 div1 Medium - PilingRectsDiv1
首先考虑如何最大化一个集合里的长方形的交。首先所有矩形都应该堆在一个角上,这样这堆矩形的交就是 \(\min\{x\} \times \min\{y\}\)。其次应该通过旋转使得每个矩形两个参数的大小关系一致,也就是所有矩形都满足 \(a \le b\),否则交换 \(a, b\)。
证明
考虑两个矩形 $(a, b)$ 和 $(c, d)$。不妨设 $a \le b, c \le d, a \le c$。否则就旋转或交换两个矩形。接下来可以发现所有本质不同的配对方式只有两种:$a - c, b - d$ 和 $a - d, b - c$。于是接下来只要证明 $\min \{ a, c \} \times \min\{ b, d \} \ge \min \{ a, d \} \times \min\{ b, c \}$。 \[\begin {equation} \begin {split} \min \{ a, c \} \times \min\{ b, d \} \ge \min \{ a, d \} \times \min\{ b, c \} &\iff a \times \min\{ b, d \} \ge a \times \min\{ b, c \} \\ &\iff \min\{ b, d \} \ge \min\{ b, c \} \end {split} \end {equation} \]于是只要证明 \(\min\{ b, d \} \ge \min\{ b, c \}\)。注意到 \(a \le c \le d\),所以来分讨 \(b\) 与 \(c, d\) 的大小关系。
- \(b \le c \le d\),此时原式为 \(b \ge b\);
- \(c \le b \le d\),此时原式为 \(b \ge c\);
- \(c \le d \le b\),此时原式为 \(d \ge c\)。
容易发现这三个式子在各自的限制条件下都是成立的。故原式成立。
容易发现两个矩形的交也是一个矩形,而且这个矩形两维的大小关系和原来两个矩形相同。于是多个矩形的情况就是拿两个矩形交一下,然后把这个交拿去和另一个矩形交,这样一直做下去。所以当每两个矩形两维大小关系都一样时答案就是最优的。
交换完之后接下来就考虑进行分配。首先能够发现最小的 \(x\) 永远是其中一个集合中的最小 \(x\)。令为第一个集合。于是我们再枚举另一个集合里的最小 \(x\) 是哪个矩形。为了方便,我们将所有矩形按 \(x\) 降序排,这样枚举了另一个集合的最小 \(x\) 后就能知道这个集合里剩下的元素都只能在这个元素之前。接下来还要分配 \(y\)。设当前枚举到 \(i\),则就是把前面最大的 \(n - 1\) 个 \(y\) 分给第二个集合,或者前面把最大的 \(i - n\) 个最大的 \(y\) 和后面所有 \(y\) 分配给第一个集合。这样就要维护一个动态第 \(k\) 大。随便拿个什么东西乱搞就可以了。我这里使用的是离散化 + 权值线段树上二分。
代码
#include <iostream>
#include <algorithm>
#include <map>
#define int long long
using namespace std;
const int N = 1000000000;
const int inf = 2147483647;
pair<int, int> xy[1000005];
int n;
int XA, XB, XC, YA, YB, YC;
int x[1000005], y[1000005];
int XS[1000005], YS[1000005];
int suf[1000005];
int rt;
struct node {
int l, r, s;
} T[2000005];
struct Segment_Tree {
int ncnt;
void Insert(int& o, int l, int r, int v) {
if (!o)
o = ++ncnt;
T[o].s++;
if (l == r)
return;
int mid = (l + r) >> 1;
if (v <= mid)
Insert(T[o].l, l, mid, v);
else
Insert(T[o].r, mid + 1, r, v);
}
int Kth(int o, int l, int r, int k) {
if (!o)
return 0;
if (l == r)
return l;
int mid = (l + r) >> 1;
if (T[T[o].r].s >= k)
return Kth(T[o].r, mid + 1, r, k);
else
return Kth(T[o].l, l, mid, k - T[T[o].r].s);
}
} seg;
map<int, int> mp;
int _mp[1000005];
int d[1000005];
signed main() {
cin >> n;
int lengthofXS;
cin >> lengthofXS;
for (int i = 0; i < lengthofXS; i++) cin >> XS[i];
cin >> lengthofXS;
for (int i = 0; i < lengthofXS; i++) cin >> YS[i];
cin >> XA >> XB >> XC >> YA >> YB >> YC;
for (int i = 0; i < (lengthofXS); i++) {
x[i] = XS[i];
y[i] = YS[i];
}
for (int i = (lengthofXS); i < 2 * n; i++) {
x[i] = (x[i - 1] * XA + XB) % XC + 1;
y[i] = (y[i - 1] * YA + YB) % YC + 1;
}
for (int i = n * 2; i; i--) x[i] = x[i - 1], y[i] = y[i - 1];
for (int i = 1; i <= n * 2; i++) {
if (x[i] > y[i])
swap(x[i], y[i]);
xy[i] = make_pair(x[i], y[i]);
d[i] = y[i];
}
sort(d + 1, d + n * 2 + 1);
for (int i = 1; i <= n * 2; i++) _mp[mp[d[i]] = mp[d[i - 1]] + (d[i] != d[i - 1])] = d[i];
mp[0] = inf;
sort(xy + 1, xy + n * 2 + 1, greater<pair<int, int> >());
suf[n * 2 + 1] = inf;
for (int i = n * 2; i; i--) suf[i] = min(suf[i + 1], xy[i].second);
for (int i = 1; i < n; i++) seg.Insert(rt, 1, n * 2, mp[xy[i].second]);
int mn = inf;
int ans = 0;
for (int i = 1; i < n; i++) mn = min(mn, xy[i].second);
for (int i = n; i < n * 2; i++) {
if (i != n) {
int a = _mp[seg.Kth(rt, 1, n * 2, n - 1)], b = _mp[seg.Kth(rt, 1, n * 2, i - n)];
int c = xy[i].second, d = suf[i + 1];
ans = max(ans, max(xy[i].first * min(a, c) + min(mn, d) * xy[n * 2].first, xy[i].first * min(mn, c) + xy[n * 2].first * min(b, d)));
} else
ans = max(ans, xy[i].first * min(mn, xy[i].second) + suf[i + 1] * xy[n * 2].first);
seg.Insert(rt, 1, n * 2, mp[xy[i].second]);
mn = min(mn, xy[i].second);
}
cout << ans << "\n";
return 0;
}
T4
Topcoder SRM 582 div1 Medium - ColorfulBuilding
考虑 dp,从大到小加入每个建筑,这样如果要新加的建筑被看到就必须加在最左边。但是最左边的建筑颜色不确定,所以还要知道最左边建筑当前颜色。设 \(dp[i][j][k]\) 表示加入了前 \(i\) 高的建筑,一共有 \(j\) 个建筑能够被看到,最左边的建筑颜色为 \(k\) 的方案数。设当前要转移的状态为 \(dp[i][j][k]\),要摆的建筑颜色为 \(cur\)。首先如果当前建筑不摆最左边,则有 \(dp[i + 1][j][k] \leftarrow dp[i][j][k] * k\)。如果摆最左边而 \(k \ne cur\),则 \(dp[i + 1][j + 1][cur] \leftarrow dp[i][j][k]\),否则 \(dp[i + 1][j][cur] \leftarrow dp[i][j][k]\)。这样就有一个朴素的 \(n^3\) dp。考虑优化。发现对于一个 \(dp[i][j][k]\),它只有对 \(dp[i + 1][\cdots][cur]\) 的贡献是特殊的。对于其他的都是乘上一个 \(i\)。所以可以使用懒标记的思想,对所有 \(cur\) 之外的颜色打一个标记,之后用的时候再乘上。接下来考虑对 \(dp[i + 1][j][cur]\) 的转移。发现这个状态首先受到 \(dp[i][j][cur] \times (i + 1)\) 的贡献。其次还受到 \(dp[i][j][\forall k \ne cur]\) 的贡献。所以我们对每种颜色维护懒标记,对每种 \(j\) 维护所有 \(k\) 的和 \(s\),每次转移时先算出当前的 \(dp[i][\cdots][k]\),然后枚举 \(j\) 进行转移,转移时维护 \(s\) 即可。然后再给每个不为 \(cur\) 的颜色打上懒标记。这样时间复杂度就是 \(\mathcal{O}(n^2)\) 的。
代码
#include <iostream>
#include <map>
#define int long long
using namespace std;
const int P = 1000000009;
int n;
string s1 = " ", s2 = " ", ss;
int clr[1305];
int dp[1305][1305];
map<int, int> mp;
int mul[1305];
int s[1305];
int ccnt;
signed main() {
cin >> n;
while (n--) {
cin >> ss;
s1 += ss;
}
cin >> n;
while (n--) {
cin >> ss;
s2 += ss;
}
n = s2.size() - 1;
for (int i = 1; i <= n; i++) {
int tmp = (s1[i] - 'a') * 10000 + s2[i] - 'a';
if (!mp.count(tmp))
mp[tmp] = ++ccnt;
clr[i] = mp[tmp];
}
for (int i = 1; i <= n / 2; i++) swap(clr[i], clr[n - i + 1]);
for (int i = 0; i < n; i++) clr[i] = clr[i + 1];
dp[1][clr[0]] = 1;
s[1] = 1;
for (int i = 1; i <= ccnt; i++) mul[i] = 1;
for (int i = 1; i < n; i++) {
int cur = clr[i];
for (int j = 1; j <= n; j++)
dp[j][cur] = (dp[j][cur] * mul[cur]) % P;
mul[cur] = 1;
for (int j = n; j; j--) {
s[j] = (s[j] * i) % P;
s[j] += dp[j][cur] + s[j - 1] - dp[j - 1][cur];
s[j] = (s[j] + P) % P;
dp[j][cur] = (dp[j][cur] * (i + 1)) % P;
dp[j][cur] += s[j - 1] - dp[j - 1][cur];
dp[j][cur] = (dp[j][cur] + P) % P;
}
for (int j = 1; j <= ccnt; j++) {
if (j != cur)
mul[j] = (mul[j] * i) % P;
}
// 三次方暴力
// for (int j = 1; j <= n; j++) {
// for (int k = 1; k <= ccnt; k++) {
// if (clr[i + 1] == k)
// dp[i + 1][j][k] = (dp[i + 1][j][k] + (i + 1) * dp[i][j][k]) % P;
// else {
// dp[i + 1][j][k] = i * dp[i][j][k] % P;
// dp[i + 1][j + 1][clr[i + 1]] = (dp[i + 1][j + 1][clr[i + 1]] + dp[i][j][k]) % P;
// }
// }
// }
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= ccnt; j++)
dp[i][j] = (dp[i][j] * mul[j]) % P;
}
int l;
cin >> l;
int ans = 0;
for (int i = 1; i <= ccnt; i++) ans += dp[l][i];
cout << ans % P << "\n";
return 0;
}
T5
Topcoder SRM 522 div1 Hard - PointErasing
首先观察到 \(x, y\) 中至少有一维是极值时这个点就不会被删掉。所以我们以纵坐标为极值的最左边点和最右边点的横坐标为界限,将整个平面分成三个部分。首先中间部分的所有点都会被删掉,一个不留。左边部分中的点中,纵坐标与 横坐标最小的点的纵坐标(\(y_1\)) 相同的点不一定会被删掉,剩下的点一定都会被删掉。右半部分同理。接下来就是考虑这里面到底有几个点能留。先考虑左部分,则右边部分是对称的。接下来只考虑左半部分的点。发现只有纵坐标在 \(y_1\) 之上的点和在 \(y_1\) 之下的点可以删除纵坐标为 \(y_1\) 的点。这些纵坐标不为 \(y_1\) 的点构成了若干个矩形,每个矩形可以删去一段区间的点。可以发现我们选的矩形一定不会有重叠,就算有重叠也可以通过调整使得选出不重叠的矩形达成相同的效果。所以直接考虑 dp。设 \(dp[i][j]\) 表示前 \(i\) 个点中留下 \(j\) 个点是否可行。转移考虑是否删去这个点。删去就枚举所有以这个点为删去的右端点的矩形进行转移,不删就从 \(dp[i - 1][j - 1]\) 里转移。
代码
#include <iostream>
#include <string.h>
#include <vector>
using namespace std;
int n;
int mn = 2147483647, mx, mnp1, mnp2, mxp1, mxp2;
int a[55];
int id[55], _id[55], ncnt;
vector<int> vec[55];
int b;
bool dp[55][55];
bool ok1[55], ok2[55], ok[55];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (a[i] > mx)
mx = a[i], mxp1 = mxp2 = i;
else if (a[i] == mx)
mxp2 = i;
if (a[i] < mn)
mn = a[i], mnp1 = mnp2 = i;
else if (a[i] == mn)
mnp2 = i;
}
int L = min(mnp1, mxp1), R = max(mnp2, mxp2);
for (int i = 1; i <= n; i++) b += (a[i] == mx || a[i] == mn);
for (int i = 1; i <= L; i++) {
if (a[i] == a[1] && i != L)
_id[id[i] = ++ncnt] = i;
if (a[i] > a[1] || i == L) {
for (int j = 1; j < i; j++) {
if (a[j] < a[1]) {
int lm = 0, rm = 0, cnt = 0;
for (int k = j + 1; k < i; k++) {
if (a[k] == a[1]) {
rm = k;
++cnt;
if (!lm)
lm = k;
}
}
vec[id[rm]].emplace_back(id[lm]);
}
}
}
if (a[i] < a[1] || i == L) {
for (int j = 1; j < i; j++) {
if (a[j] > a[1]) {
int lm = 0, rm = 0, cnt = 0;
for (int k = j + 1; k < i; k++) {
if (a[k] == a[1]) {
rm = k;
++cnt;
if (!lm)
lm = k;
}
}
vec[id[rm]].emplace_back(id[lm]);
}
}
}
}
dp[0][0] = 1;
for (int i = 1; i <= ncnt; i++) {
for (int j = 1; j <= ncnt; j++) {
dp[i][j] |= dp[i - 1][j - 1];
for (auto v : vec[i])
dp[i][j] |= dp[v - 1][j];
}
}
for (int i = 0; i <= ncnt; i++) ok1[i] = dp[ncnt][i];
for (int i = 1; i <= ncnt; i++) vec[i].clear();
ncnt = 0;
for (int i = n; i >= R; i--) {
if (a[i] == a[n] && i != R)
_id[id[i] = ++ncnt] = i;
if (a[i] > a[n] || i == R) {
for (int j = i + 1; j <= n; j++) {
if (a[j] < a[n]) {
int lm = 0, rm = 0, cnt = 0;
for (int k = i + 1; k < j; k++) {
if (a[k] == a[n]) {
rm = k;
++cnt;
if (!lm)
lm = k;
}
}
vec[id[lm]].emplace_back(id[rm]);
}
}
}
if (a[i] < a[n] || i == R) {
for (int j = i + 1; j <= n; j++) {
if (a[j] > a[n]) {
int lm = 0, rm = 0, cnt = 0;
for (int k = i + 1; k < j; k++) {
if (a[k] == a[n]) {
rm = k;
++cnt;
if (!lm)
lm = k;
}
}
vec[id[lm]].emplace_back(id[rm]);
}
}
}
}
memset(dp, 0, sizeof dp);
dp[0][0] = 1;
for (int i = 1; i <= ncnt; i++) {
for (int j = 1; j <= ncnt; j++) {
dp[i][j] |= dp[i - 1][j - 1];
for (auto v : vec[i])
dp[i][j] |= dp[v - 1][j];
}
}
for (int i = 0; i <= ncnt; i++) ok2[i] = dp[ncnt][i];
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n; j++) {
if (ok1[i] && ok2[j])
ok[i + j + b] = 1;
}
}
for (int i = 1; i <= n; i++) {
if (ok[i])
cout << i << " ";
}
cout << "\n";
return 0;
}
脑洞要打开。
dp 时可以选择要知道什么就把什么扔到状态里。优化时可以只考虑特殊的转移,平凡的转移可以使用懒标记。
多观察性质。多猜结论。
标签:min,int,++,xy,20240401,矩形,dp From: https://www.cnblogs.com/forgotmyhandle/p/18111716