K soyorin的通知
完全背包+加不少于的模型
由于人数只有1000,那么 bi 实际有效的范围只有1000左右,并且,soyorin至少要花一次 p 的代价将消息通知给 1 个人,然后再让这个人去将消息通知给剩下的 n−1 个人。
那么问题就转化成了:将消息通知给 n−1 个人的最小代价,将消息通知给 bi 个人需要花费 ai 的代价,且 ai,bi 能用多次,也就是一个完全背包。
注意一些细节
int f[N];
void solve()
{
int n, p;
cin >> n >> p;
memset(f, 0x3f, sizeof f);
f[0] = 0;
for (int i = 1; i <= n; i++)
{
int a, b;
cin >> a >> b;
if (b >= n)
b = n - 1; // b可能会大于你,但是必须选择一个别人,故最多n-1个
for (int j = 0; j <= n; j++) // 这一步从0开始枚举,其实就是,f[i][j]表示前i个选,人数不小于j的最少花费
f[j] = min(f[j], f[max(0, j - b)] + a);
}
int ans = 1e9;
for (int i = 0; i < n; i++)
ans = min(ans, f[i] + (n - i) * p);
cout << ans << endl;
}
J: rikki的数组陡峭值
思路:贪心加dp,就是把区间能合并的先进性合并,对于不能合并的区间也就是没有交集的区间,
那么最优情况一定是取其左右端点,这里进行一个简单线性dp即可,表示前i个区间并且第i个区间取左/右端点时的最小陡峭值
具体的证明:不会,类似取区间,与后面求交一定不会更优,与前面求交一定不会更劣。结论是对于一个区间,如果前后两个交集都已知,这个区间既跟前一个交集相交又跟后一个交集相交,但前后两个交集不相交。那么这个区间一定包含了在前后两个交集之间空出的一部分,所以这个区间放在前面和后面都不会影响两个交集之间的陡峭值,但如果放在后面,可能会导致交集变小,导致后续增加区间的数量减少。
如果两个区间相交,而与前后不相交,那么我们取这两个区间的交集,将会是最优解,因为这样这两个区间少了陡峭值,和其他区间的陡峭值并未改变。
但是似乎不写dp也可以
dp:
int n;
PII p[N];
int f[N][2];
vector<PII> g;
void solve()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> p[i].xx >> p[i].yy;
}
int L = p[1].xx, R = p[1].yy;
for (int i = 2; i <= n; i++)
{
int l = p[i].xx, r = p[i].yy;
if (l <= R && r >= L)
{
L = max(l, L);
R = min(r, R);
}
else
{
g.pb({L, R});
L = l, R = r;
}
}
g.pb({L, R});
for (int i = 1; i < g.size(); i++)
{
f[i][0] = min(f[i - 1][0] + abs(g[i].xx - g[i - 1].xx), f[i - 1][1] + abs(g[i].xx - g[i - 1].yy));
f[i][1] = min(f[i - 1][1] + abs(g[i].yy - g[i - 1].yy), f[i - 1][0] + abs(g[i].yy - g[i - 1].xx));
}
int t = g.size() - 1;
cout << min(f[t][0], f[t][1]) << endl;
}
非dp:
其实就是记录一下上一个区间,以及上一次是点还是区间
void solve()
{
int n;
cin >> n;
int l = 0, r = 1e9 + 5, pos;
int flag = 1;
int ans = 0;
for(int i = 1;i <= n;i++)
{
int x, y;
cin >> x >> y;
if(flag)
{
if(x >= r)
{
flag = 0;
ans += x - r;
pos = x;
}
else if(y <= l)
{
flag = 0;
ans += l - y;
pos = y;
}
else
{
l = max(l, x);
r = min(r, y);
}
}
else
{
if(x <= pos && y >= pos)
continue;
else if(x > pos)
{
ans += x - pos;
pos = x;
}
else
{
ans += pos - y;
pos = y;
}
}
}
cout << ans << '\n';
}
B: tomorin的字符串迷茫值
只有"mygo","m_ygo","my_go","myg_o","m_y_go","m_yg_o","my_g_o","m_y_g_o"这 8 种子串可以通过删除得到"mygo"子串。
那么,我们可以枚举上述 8 种子串,子串内的删除方法是唯一确定的,子串两边可以任意删除,这个子串对答案的贡献就是两边任意删除的方案数相乘。而子串两边任意删除的方案数可以通过动态规划预处理(其实是斐波那契数列)。
时间复杂度
string s;
int f[N];
void solve()
{
cin >> s;
int n = s.size();
s = " " + s ;
f[0] = 1;
f[1] = 2;
for (int i = 2; i <= n + 1; i++)
f[i] = (f[i - 1] + f[i - 2]) % mod;
int ans = 0;
vector<string> t = {"mygo", "m ygo", "my go", "myg o", "m y go", "m yg o", "my g o", "m y g o"};
auto check = [&](string s, string t) -> bool
{
for (int i = 0; i < t.size(); i++)
{
if (t[i] == ' ')
continue;
if (s[i] != t[i])
return 0;
}
return 1;
};
for (int i = 1; i <= n; i++)
{
for (auto sub : t)
{
string p = s.substr(i, sub.size());
if (check(p, sub))
ans = (ans + f[i - 1] * f[n - (i + sub.size() - 1)] % mod) % mod;
}
}
cout << ans % mod << endl;
}