LGP17194 KOI 2026 #2 分发零食
原题链接:KOI 2026 #2 分发零食
分析
和 这个 有点像,不过不太一样。
其实抽象一下就是拓扑排序,但是,场上搓出来的东西......也类似吧......就是这个是 deg[i]=1 的时候出列......
获得了 97 p t s 97pts 97pts。继续努力吧。
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200005;
int n;
set<int> pos[N];
bool in_ans[N]; //id
bool vis[N]; //snack
vector<int> ans;
struct node{
int id;
};
int c[N];
bool operator<(const node &tmp1, const node &tmp2){
return c[tmp1.id] < c[tmp2.id];
}
priority_queue<node> q;
vector<int> a[N];
signed main(){
freopen("snack.in", "r", stdin);
freopen("snack.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++){
cin >> c[i];
for (int j = 1, aij; j <= c[i]; j++){
cin >> aij;
a[aij].push_back(i);
pos[i].insert(aij);
}
if (c[i] == 1){
q.push({i});
}
}
if (q.empty()){
cout << -1;
return 0;
}
while (!q.empty()){
int cur = q.top().id;
q.pop();
if (in_ans[cur])
continue;
if (c[cur] != 1){
cout << -1;
return 0;
}
ans.push_back(cur);
in_ans[cur] = true;
for (auto v : pos[cur]){
for (auto vv : a[v]){
if (in_ans[vv])
continue;
pos[vv].erase(v);
c[vv]--;
if (c[vv] == 0){
cout << -1;
return 0;
}
if (c[vv] == 1){
q.push({vv});
}
}
break;
}
}
if (ans.size() != n){
cout << -1;
return 0;
}
for (auto v : ans){
cout << v << " ";
}
}
CF2205E Simons and Dividing the Rhythm
原题链接:E. Simons and Dividing the Rhythm
分析
我场上这么烫吗?
需要改一个习惯啊,就是,当我们一次性没有通过 T 1 T1 T1,不要惊慌,特别是,你都过了 T 1 T1 T1 了,还搁这烫?
我们直接去 T T T 上做。不是哥们,对于小数据,你真的不会写吗?就是这个位置要不要断开?然后,你把这个直接扔到 set 里,我还不信过不了 5 5 5 分......那你 r a t i n g rating rating 不就上 1700 1700 1700 了?哎,不过后悔没有意义。
我们来尝试把这个东西设出来,然后转移,然后......
我们设 d p i , j dp_{i,j} dpi,j 表示考虑了 T T T 的前 i i i 位,分成了 j j j 段的对应的 S S S 的数量。
转移的话,肯定是从 d p i ′ , j − 1 dp_{i',j-1} dpi′,j−1,其中 i ′ < i i' < i i′<i。
值怎么搞呢......
思考一下,我们需要找的就是 border \operatorname{border} border,这个东西,然后,我们设 d p i dp_{i} dpi 表示这一段以 i i i 结尾。转移需要找那些 j , i j,i j,i 没有 border \operatorname{border} border 的,这样不至于算重。
正解
cpp
#include <bits/stdc++.h>
#define int long long
#define mod 998244353
using namespace std;
const int N = 8005;
int T;
int n, t[N];
int dp[N];
int border[N][N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
for (int cs = 1; cs <= T; cs++){
cin >> n;
for (int i = 1; i <= n; i++){
cin >> t[i];
dp[i] = 0;
}
for (int i = 1; i <= n; i++){
border[i][i] = 0;
for (int j = i + 1, cur; j <= n; j++){
cur = border[i][j - 1];
while (cur && t[cur + 1] != t[j]){
cur = border[i][cur];
}
if (cur){
border[i][j] = cur + 1;
}
else{
if (t[i] == t[j])
border[i][j] = i;
else
border[i][j] = 0;
}
}
}
dp[0] = 1;
for (int i = 1; i <= n; i++){
for (int j = 1; j <= i; j++){
if (!border[j][i])
dp[i] = (dp[i] + dp[j - 1]) % mod;
}
}
cout << dp[n] << '\n';
}
}
LGP6847 CEOI 2019 Magic Tree
原题链接:CEOI 2019 Magic Tree
分析
看一个启发式合并的题解吧......
首先,我们要了解一下树上启发式合并......按照 p y d pyd pyd 之前的意思,应该就是按照自己想要的直接合并......说得有点笼统。其实理解的很到位。
对,暴力的 DP 是想到了的......
就是设 d p u , t dp_{u,t} dpu,t 表示 t t t 时刻,断开 u u u 和 f a u fa_u fau 连边的最大值。
转移我也想到了,
d p u , t = v a l u , t + ∑ v ∈ s o n u d p v , t dp_{u,t}=val_{u,t}+\sum\limits_{v\in son_u}{dp_{v,t}} dpu,t=valu,t+v∈sonu∑dpv,t
其中,
v a l u , t = w u × d u ≤ t val_{u,t}=w_u\times d_u\\le t valu,t=wu×du≤t
d p u dp_u dpu 显然单调不降(个人认为如果真的可以从某些角度套用 slope trick,那么,肯定可以反悔贪心)。
可以使用 map 维护拐点,然后可以合并 map,然后,还有就是说 map 还需要维护差分,这个是会超时的,所以,我们可以直接 dsu on tree。
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 100005;
int n, m, k;
int p[N];
int d[N], w[N];
map<int, int> dp[N];
int fa[N];
void merge(int &a, int b){
if (dp[a].size() < dp[b].size())
swap(a, b);
for (auto it = dp[b].begin(); it != dp[b].end(); it++){
dp[a][it -> first] += it -> second;
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> k;
for (int i = 2; i <= n; i++){
cin >> p[i];
}
for (int i = 1, v; i <= m; i++){
cin >> v;
cin >> d[v] >> w[v];
}
for (int i = 1; i <= n; i++){
fa[i] = i;
}
for (int i = n; i >= 1; i--){
dp[fa[i]][d[i]] += w[i];
for (auto it = dp[fa[i]].upper_bound(d[i]); it != dp[fa[i]].end(); ){
if (it -> second > w[i]){
it -> second -= w[i];
break;
}
w[i] -= it -> second;
auto tmp = it;
it++;
dp[fa[i]].erase(tmp);
}
merge(fa[p[i]], fa[i]);
}
int ans = 0;
for (auto it = dp[fa[1]].begin(); it != dp[fa[1]].end(); it++){
ans += it -> second;
}
cout << ans;
}
LGP9522 JOIST 2022 错误拼写 / Misspelling
原题链接:JOIST 2022 错误拼写 / Misspelling
分析
场上觉得是什么差分约束,但是,不会做,看看题解吧。
不是哥们,这么自然吗?我感觉我场上应该写出来 O ( n 2 ) O(n^2) O(n2) 做法的😭
那不是可以获得将近 60 p t s 60pts 60pts!
场上我们能想到了,对于两个字典序不同,假设题目给出了 T a ≤ T b T_a\le T_b Ta≤Tb,那么,必有 i ∈ a , b − 1 i\ina,b-1 i∈a,b−1,满足第一个 S i ≠ S i + 1 S_i\neq S_{i+1} Si=Si+1,使得 S i > S i + 1 S_i>S_{i+1} Si>Si+1,记为限制一; T a > T b T_a>T_b Ta>Tb 同理,即为限制二。
设 d p i , j dp_{i,j} dpi,j 表示 i i i 是一个段的开头,填字符 j j j 的方案数,其中, i − 1 i-1 i−1 与 i i i 填写的不同。
如果从 d p k , j ′ dp_{k,j'} dpk,j′ 进行转移,需要分讨:
- 若 j ′ > j j'>j j′>j,则不存在 k ≤ l < i , i − 1 ≤ r k\le l<i,i-1\le r k≤l<i,i−1≤r 的限制二
- 若 j ′ < j j'<j j′<j,则不存在 k ≤ l < i , i − 1 ≤ r k\le l<i,i-1\le r k≤l<i,i−1≤r 的限制一
由于 k 是一段后缀,可以使用 set 维护。
然后,使用前缀和优化 DP,可以做到 O ( V n ) O(Vn) O(Vn),其中, V = 26 V=26 V=26。
正解
cpp
#include <bits/stdc++.h>
#define int long long
#define mod 1000000007
using namespace std;
const int N = 500005, V = 26;
int n, m;
int a[N], b[N];
int dp[N][V];
vector<int> ins1[N], ins2[N], era1[N], era2[N];
multiset<int> l, r;
int sum[N][V];
int f[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= m; i++){
cin >> a[i] >> b[i];
if (a[i] < b[i]){
ins1[a[i]].push_back(i);
era1[b[i] - 1].push_back(i);
}
if (a[i] > b[i]){
ins2[b[i]].push_back(i);
era2[a[i] - 1].push_back(i);
}
}
for (int i = 1; i <= (int)ins1[1].size(); i++){
l.insert(1);
}
for (int i = 1; i <= (int)ins2[1].size(); i++){
r.insert(1);
}
for (int i = 0; i < V; i++)
dp[1][i] = sum[1][i] = 1;
for (int i = 2; i <= n; i++){
int tmp;
if (r.empty()){
tmp = 1;
}
else{
tmp = *--r.end() + 1;
}
int tot = 0;
for (int j = 0; j < V; j++){
f[j] = (sum[i - 1][j] - sum[tmp - 1][j]) % mod;
}
for (int j = V - 1; j >= 0; j--){
dp[i][j] = (dp[i][j] + tot) % mod;
tot = (tot + f[j]) % mod;
}
if (l.empty()){
tmp = 1;
}
else{
tmp = *--l.end() + 1;
}
tot = 0;
for (int j = 0; j < V; j++){
f[j] = (sum[i - 1][j] - sum[tmp - 1][j]) % mod;
}
for (int j = 0; j < V; j++){
dp[i][j] = (dp[i][j] + tot) % mod;
tot = (tot + f[j]) % mod;
}
for (int j = 0; j < V; j++){
sum[i][j] = (sum[i - 1][j] + dp[i][j]) % mod;
}
for (auto v : era1[i - 1]){
l.erase(l.find(a[v]));
}
for (auto v : era2[i - 1]){
r.erase(r.find(b[v]));
}
for (int j = 1; j <= (int)ins1[i].size(); j++){
l.insert(i);
}
for (int j = 1; j <= (int)ins2[i].size(); j++){
r.insert(i);
}
}
int ans = 0;
for (int i = 1; i <= n; i++){
for (int j = 0; j < 26; j++){
ans = (ans + dp[i][j]) % mod;
}
}
cout << (ans + mod) % mod;
}