做题记录
CF2008 G. Sakurako's Task
纪念第一次场上切的一道 G 题,想了
题意
给定数组
问 数组 a 中不存在的第
思路
首先发现这个第
转变思路从操作入手,发现如果我们可以获得一个很小的数
那么思考如何得到最小的
Artoj P3183. 游戏升级
模拟赛的好题。
题目大意
t 组询问,每次给定
整除分块很一眼,然后想法是先对
但是 哈希会超时。
然后可以想到先 对
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define PII pair<int,int>
#define rep(k,a,b) for (int k = a;k <= b;k++)
#define mem memset
int rd()
{
int f=1,k=0;char c = getchar();
while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
return k*f;
}
int T,n,a1,a2,b1,b2,c;
unordered_map<int,PII> mp;
int calc(int l,int r,int x,int y)
{
if (x > r ||y <l) return 0;
if (l <= x && y <= r) return y-x +1;
if (x <= l && r <= y) return r-l+1;
if (x <= r && l <= x) return r-x+1;
if (y >= l && y <= r) return y-l+1;
}
struct node{int x,l,r;};
int main()
{
cin >>T;
vector<node>p1,p2;
while (T--)
{
//mp.clear();
p1.clear(),p2.clear();
cin >> a1 >> b1 >> a2 >> b2 >>n;
if (a1 < a2) swap(a1,a2),swap(b1,b2);
c= b2-b1;
int ans = 0;
for (int l = 1,r;l <= n;l=r+1)
{
if (l > a2)
{
p2.push_back({0,l,n});
break;
}
r = (a2/(a2/l));
r = min(r,n);
p2.push_back({a2/l,l,r});
}
for (int l = 1,r;l <= n;l=r+1)
{
if (l > a1)
{
p1.push_back({0,l,n});
break;
}
r = (a1/(a1/l));
r = min(r,n);
p1.push_back({a1/l,l,r});
}
reverse(p1.begin(),p1.end());
reverse(p2.begin(),p2.end());
int cnt1 = p1.size(),cnt2 = p2.size();
int j = 0;
rep(i,0,cnt1-1)
{
while (j < cnt2-1 && p2[j].x < p1[i].x-c) j++;
if (p2[j].x == p1[i].x-c)
{
ans += calc(p2[j].l,p2[j].r,p1[i].l,p1[i].r);
}
}
cout << ans << endl;
}
return 0;
}
Artoj P3184. 难题
模拟赛的好题,因没有特判挂了60 pts。
解法
可以发现构成
做到这里以为没了,但实际上有个地方没有考虑到:算法在枚举斐波那契数列时,对于不同的 a,b,算出来的 x,y 是否可能重复。
我们假设存在:
我们发现解出来
但是比较特别的是当
本题就做完了。
三元组
模拟赛遇到的好题,考验对
一句话题意:给定一个序列
std 是枚举
做法
枚举
因为前几位相同时结果一定是相同的。
于是我们可以在trie树上枚举
而 j 可选的条件是
因此对于每个
可以将贡献拆开,每个节点维护
此时对于每个 k 都要预处理出每一位,复杂度
考虑优化,我们发现对于每个
具体实现为对于
还有保存一个
然后枚举 i,贡献为
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i,a,b) for (int i = a;i <= b;i++)
#define ll long long
const int N = 5e5+5;
int n,a[N];
int son[N*32][2];
int cnt[N*32][2];
ll sum[N*32][2];
int tot;
ll pre[N][35][2];
void insert(int x,int id)
{
//cout << "insert" << x << endl;
int p = 0;
for (int i = 30;i >= 0;i--)
{
int ch = x >> i & 1;
if (!son[p][ch]) son[p][ch] = ++tot;
p = son[p][ch];
for (int c = 0;c < 2;c++)
cnt[p][c]++,sum[p][c] += pre[id][i][c];
// cout << p << ' ';
//cout << id << ' ' << i << ' ' << ch << endl;
}
//puts("");
}
int query(int id)
{
int p = 0;
ll res = 0;
for (int i = 30;i >= 0;i--)
{
int ch = a[id]>>i&1;
//cout << i << endl;
if (son[p][!ch])
{
// cout << i << endl;
int t2 = son[p][!ch],c = ch;
// cout <<t2 << ' ' << ch << endl;
res += 1ll*sum[t2][ch] - 1ll*cnt[t2][ch] * pre[id][i][c];
}
if (son[p][ch])
p = son[p][ch];
else break;
}
return res;
}
signed main()
{
cin >> n;
for (int i = 1;i <= n;i++) scanf("%lld",&a[i]);
for (int i = 1;i <= n;i++)
for (int j = 30;j >= 0;j--)
pre[i][j][(a[i]>>j&1)] = 1;
for (int j = 30;j >= 0;j--)
for (int k = 0;k < 2;k++)
for (int i = 1;i <= n;i++)
pre[i][j][k] += pre[i-1][j][k];
// for (int i = 1;i <= n;i++)
// {
// rep(j,0,3)
// rep(k,0,1)
// {
// cout << i << ' ' << j << ' ' << k << ' ' << pre[i][j][k] << endl;
// }
// }
ll ans = 0;
for (int i = n;i >= 1;i--)
{
// cout << query(i)<<endl;
ans += query(i);
insert(a[i],i);
}
cout << ans << endl;
return 0;
}
P4284 [SHOI2014] 概率充电器
感觉是一道非常好的题啊。
树形 DP + 概率期望。
题意:给定一棵树,每个点有概率自己带电,每条边也有概率可以传导电,问期望带电的点的个数。
做法
首先是一个非常简单经典的
于是考虑在树上做树形 DP。
首先我们可以设出
这个转移非常好想:
从反面考虑一下就做完了。
然后需要考虑父亲对当前
我们发现父亲能对
因此设
那么转移就为:
code
// Problem: P4284 [SHOI2014] 概率充电器
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4284
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// Author: Eason
// Date:2024-09-26 16:32:38
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define PII pair<int,int>
#define rep(k,a,b) for (int k = a;k <= b;k++)
#define adde(a,b) v[a].push_back(b)
#define rd read
#define PID pair<int,double>
int read()
{
int f=1,k=0;char c = getchar();
while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
return k*f;
}
const int N = 5e5+5;
const double eps = 1e-8;
int n;
vector<PID> v[N];
double p[N];
double f[N];
double g[N];
double val[N];
int fa[N];
void dfs(int x,int fa)
{
if (v[x].size() == 1 && x != 1)
{
g[x] = p[x];
return;
}
double tp = 1;
for (auto [y,pc]:v[x])
{
if (y == fa) continue;
dfs(y,x);
val[y] = pc;
tp *= (1-(pc*g[y]));
}
tp *= (1-p[x]);
g[x] = 1-tp;
}
void dfs2(int x,int fa,double pc)
{
//cout << x << endl;
if (x != 1)
{
double p1 = f[fa];//p1表示父亲的子树除x以外的概率。
p1 = 1-p1;
if (1-g[x]*pc < eps)
{
f[x] = 1;
}
else
{
p1 /= (1-g[x]*pc);
p1 = 1-p1;
f[x] = g[x] + (p1 * pc) - g[x] * p1* pc;
}
}
for (auto [y,pc]:v[x])
{
if (y == fa) continue;
dfs2(y,x,pc);
}
}
int main()
{
cin >> n;
rep(i,1,n-1)
{
int x= rd(),y = rd(),p = rd();
double ds = p/100.0;
v[x].push_back({y,ds});
v[y].push_back({x,ds});
}
rep(i,1,n)
{
int x = rd();
p[i] = x/100.0;
}
if (n == 1)
{
cout << p[1] << endl;
return 0;
}
dfs(1,0);
f[1] = g[1];
dfs2(1,0,0);
double ans = 0;
rep(i,1,n)
{
//cout << i << ' ' << f[i]<< ' '<<g[i] << endl;
ans += f[i];
}
printf("%.6f",ans);
return 0;
}
C. Lazy Narek
VP打的,结果连 C 都做不出来 /qd /qd
题意:
给定
一开始想的
正解
非常巧妙的
为什么能这样设置呢?因为我们可以发现答案与当前是第几个字符串没有什么关系,而且从一个一个字符匹配 "narek" 的角度来思考可以解决跨字符串的问题,并且每个字符串选或不选只关系到当前匹配到第几个字符。
其实也可以看成是节省了一维的空间。
考虑转移。
枚举第
设扫完后匹配到了第
code
// Problem: C. Lazy Narek
// Contest: Codeforces - Codeforces Round 972 (Div. 2)
// URL: https://codeforces.com/contest/2005/problem/C
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// Author: Eason
// Date:2024-09-27 10:51:26
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define PII pair<int,int>
#define rep(k,a,b) for (int k = a;k <= b;k++)
#define adde(a,b) v[a].push_back(b)
#define rd read
int read()
{
int f=1,k=0;char c = getchar();
while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
return k*f;
}
const int N = 2e3+5;
int t;
int n,m;
int dp[10];
int tmp[10];
string str[N];
string narek = "narek";
int main()
{
cin >> t;
while (t--)
{
cin >> n >> m;
rep(i,1,n) cin >> str[i];
memset(dp,-INF,sizeof dp);
dp[0] = 0;
rep(i,1,n)
{
memcpy(tmp,dp,sizeof tmp);
rep(k,0,4)
{
int now = k,res = 0;
rep(j,0,m-1)
{
int idx = narek.find(str[i][j]);
if (idx == -1) continue;
if (idx != now) res--;
else res++,now = (now+1)%5;
}
tmp[now] = max(tmp[now],dp[k] + res);
}
memcpy(dp,tmp,sizeof dp);
}
int ans = 0;
for (int i = 0;i < 5;i++) ans = max(ans,dp[i]- 2 * i);
cout << ans << endl;
}
return 0;
}
关于最后为什么要写
for (int i = 0;i < 5;i++) ans = max(ans,dp[i]- 2 * i);
而不是输出
HACK:
1
2 5
ppppn
arekn
如这个数据,最后会匹配成 "narekn",因此
CSP-S 模拟赛 A. 序列
给点序列
。- 满足
。
状压+数位 DP 好题。
条件二可以转化为
考虑从高位开始给每一位填数,那么需要判断是否超过
对于每一个数字记录当前是否达到最大限制,即数位 DP 中的
用一个
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define int ll
#define PII pair<int,int>
#define rep(k,a,b) for (int k = a;k <= b;k++)
#define adde(a,b) v[a].push_back(b)
#define addev(a,b,c) v[a].push_back({b,c});
#define rd read
int read()
{
int f=1,k=0;char c = getchar();
while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
return k*f;
}
const int N = 25,M = 66,mod = 998244353;
int n,a[N];
int num[N][M];
int f[M][(1<<16)+10];
void solve()
{
cin >> n;
rep(i,1,n) a[i] = rd();
for (int i = 1;i <= n;i++)
{
for (int j = 1;j <= 60;j++)
{
num[i][j] = (a[i]>>(j-1))&1;
}
}
for (int j = 0;j < (1<<n);j++) f[0][j] = 1;
for (int i = 1;i <= 60;i++)
{
for (int j = 0;j < (1<<n);j++)
{
int cur = 0;
for (int k = 1;k <= n;k++)
{
if ((j>>(k-1)&1) && num[k][i]==0) cur += (1<<(k-1));
}
(f[i][j] += f[i-1][cur])%=mod;
for (int k = 1;k <= n;k++)
{
int sj = (j>>(k-1))&1;
int limit = 1;
if (sj == 1) limit = num[k][i];
if (limit == 0) continue;
int ncur = cur;
if (sj) ncur += (1<<k-1);
(f[i][j] += f[i-1][ncur])%=mod;
}
}
}
cout << f[60][(1<<n)-1] << endl;
}
signed main()
{
int t;t = 1;
while(t--)
{
solve();
}
return 0;
}
A. 卡牌游戏
来源:CSP-S Day 8 模拟赛
你有两个属性攻击力
还有一个
你将进行
接下来选择一项:
问
考虑计算每一项操作对于
操作一可以直接加。
对于操作三,若我们知道后面攻击了
但是操作二似乎没有那么好记录,发现增量对于
那么我们发现只需要知道
则状态设计为:
则转移就非常简单了。
感觉是一道 DP 好题,需要仔细研究状态的设计。
const int N = 105;
int n;
int a[N],b[N],c[N];
ll dp[105][105][5005];
void solve()
{
cin >> n;
for (int i = 1;i <= n;i++) a[i] = rd(),b[i] = rd(),c[i] = rd();
memset(dp,-INF,sizeof dp);
dp[n+1][0][0] = 0;
for (int i = n;i >= 1;i--)
{
for (int j = 1;j <= n-i+1;j++)
{
for (int k = i*j;k <= n*(n+1)/2;k++)
{
ll gx1 = -INF,gx2 = -INF,gx3 = -INF;
if (j >= 1 && k >= i)
gx1 = dp[i+1][j-1][k-i] + a[i];
gx2 = dp[i+1][j][k] + 1ll*j * c[i];
gx3 = dp[i+1][j][k] + 1ll*(k-j*i)*b[i];
dp[i][j][k] = max({gx1,gx2,gx3});
}
}
}
ll ans = 0;
for (int j = 1;j <= n;j++)
for (int k = 0;k <= n*(n+1)/2;k++)
ans = max(ans,dp[1][j][k]);
cout << ans << endl;
}
时间复杂度
A. 中位数
来源:2024 CSP-S 模拟赛 Day 15
你有一个序列
定义
求
对于一个大小为
一个经典的trick:
看到中位数,可以想到先二分中位数为
然后我们将大于等于
则通过我们在新序列得到的答案就可以知道答案与
在新序列上考虑原问题。
首先
同理,
然后这题就做完了。
和这题同个 trick 的经典题目:P2824 [HEOI2016/TJOI2016] 排序
题意是区间排序操作,在操作最后单点查询。
做法是二分答案,然后
那么区间排序就可以用区间赋值
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define PII pair<int,int>
#define rep(k,a,b) for (int k = a;k <= b;k++)
#define adde(a,b) v[a].push_back(b)
#define addev(a,b,c) v[a].push_back({b,c});
#define rd read
int read()
{
int f=1,k=0;char c = getchar();
while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
return k*f;
}
const int N = 1e5+5;
int n,m,p[N];
struct node
{
int op,l,r;
}Q[N];
int idx;
struct node2
{
int sum,tag=-1;
}tr[N<<2];
void stf(int k,int l,int r,int c)
{
tr[k].sum = (r-l+1) * c;
tr[k].tag = c;
}
void psd(int k,int l,int r)
{
if (tr[k].tag != -1)
{
int mid = l + r >> 1;
stf(k<<1,l,mid,tr[k].tag);
stf(k<<1|1,mid+1,r,tr[k].tag);
tr[k].tag = -1;
}
}
void modify(int k,int l,int r,int x,int y,int c)
{
if (x > r || y < l) return;
if (x <= l && r <= y)
{
stf(k,l,r,c);
return;
}
int mid = l + r >> 1;
psd(k,l,r);
modify(k<<1,l,mid,x,y,c);
modify(k<<1|1,mid+1,r,x,y,c);
tr[k].sum = tr[k<<1].sum + tr[k<<1|1].sum;
}
int query(int k,int l,int r,int x,int y)
{
if (x > r || y < l) return 0;
if (x <= l && r <= y) return tr[k].sum;
int mid = l+ r >> 1;
psd(k,l,r);
return query(k<<1,l,mid,x,y) + query(k<<1|1,mid + 1,r,x ,y);
}
void build(int k,int l,int r,int x)
{
if (l == r)
{
tr[k] = {(p[l] >= x),0};
return;
}
int mid =l + r >> 1;
build(k<<1,l,mid,x);build(k<<1|1,mid+1,r,x);
tr[k].sum = tr[k<<1].sum + tr[k<<1|1].sum;
tr[k].tag = -1;
}
int check(int x)
{
build(1,1,n,x);
for (int i = 1;i <= m;i++)
{
auto[op,l,r] = Q[i];
int cnt = query(1,1,n,l,r);
// cout << cnt << endl;
if (cnt == 0) continue;
modify(1,1,n,l,r,0);
if (op == 1) modify(1,1,n,l,l+cnt-1,1);
else modify(1,1,n,r-cnt+1,r,1);
}
return query(1,1,n,idx,idx);
}
void solve()
{
cin >> n >> m;
for (int i = 1;i <= n;i++) p[i] = rd();
rep(i,1,m)
{
Q[i] = {rd(),rd(),rd()};
}
idx = rd();
int l = 1,r = 1e5;
while (l < r)
{
int mid = l + r+1 >> 1;
if (check(mid)) l = mid;
else r = mid -1;
}
//cout << check(6) << endl;
cout << l << endl;
}
int main()
{
int t;t = 1;
while(t--)
{
solve();
}
return 0;
}
[ABC282Ex] Min + Sum
一道最小值分治的 trick。
又称笛卡尔树分治。
题意:求 (l,r) 对数满足 且
做法考虑分治,将
然后将将原区间
我们发现由于区间和的单调性,在确定
同时为了保证复杂度,我们枚举左右中长度较小的区间,于是本题就做完了。
复杂度分析
题解区还看到一种更nb的做法,直接用单调栈算出该值作为最小值的最左和最右,然后直接枚举左右区间中更小的区间,二分计算。
这两种方法的复杂度都可以用从笛卡尔树的角度证明。
考虑每个位置
设当前区间节点为
那么在枚举完子树
而又有
也就是说每个位置最多被枚举
类似启发式合并。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define int ll
#define PII pair<int,int>
#define rep(k,a,b) for (int k = a;k <= b;k++)
#define mem memset
#define rd read
int read()
{
int f=1,k=0;char c = getchar();
while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
return k*f;
}
const int N = 2e5+5;
int n,a[N],b[N];
int sum[N];
int S;
int dp[N][25];
void prework()
{
for (int i = 1;i <= n;i++) dp[i][0] = i;
for (int j = 1;j <= 20;j++)
for (int i = 1;(i+(1<<j)-1) <= n;i++)
{
int t1 = dp[i][j-1],t2 = dp[i+(1<<(j-1))][j-1];
if (a[t1] < a[t2]) dp[i][j] = t1;
else dp[i][j] = t2;
}
}
int query(int l,int r)
{
int k = log2(r-l+1);
int t1 = dp[l][k],t2 = dp[r-(1<<k)+1][k];
return ((a[t1] < a[t2])?t1:t2);
}
int ans;
int calcl(int id,int l,int r,int num)
{
l--;
while (l < r)
{
int mid = l + r + 1>> 1;
if (sum[mid] - sum[id-1] <= num) l = mid;
else r = mid - 1;
}
return l;
}
int calcr(int id,int l,int r,int num)
{
r++;
while (l < r)
{
int mid =l + r>>1;
if (sum[id]-sum[mid-1] <= num) r = mid;
else l = mid + 1;
}
return l;
}
void solve(int l,int r)
{
if (l > r) return;
int mid = query(l,r);
if (mid-l < r-mid)
{
for (int i = l;i <= mid;i++) ans += calcl(i,mid,r,S-a[mid]) - mid+1;
}
else{
for (int i = mid;i <= r;i++) ans += mid - calcr(i,l,mid,S-a[mid]) + 1;
}solve(l,mid-1);solve(mid+1,r);
}
signed main()
{
cin >> n >> S;
rep(i,1,n) a[i] =rd();
rep(i,1,n) b[i] =rd(),sum[i] = sum[i-1] + b[i];
prework();
solve(1,n);
cout << ans << endl;
return 0;
}
CF1956D Nene and the Mex Operator
*2000
贼有趣的
题意
给定长度为
分析
题目要求求出最大值并给出构造方案。
我们先思考题目给出的操作有什么性质。
由于题目给出的限制
通过手玩样例,我们猜测对于任意区间
等下再证明这玩意,先看看知道了这条性质怎么求出最大值。
考虑
过程中记录一下上次的转移点,就可以知道操作了哪些区间。
再考虑刚才的操作,我们假设区间长度
而最后的状态是
想要达成这个状态,必不可少的是
设
设
我们发现
于是我们可以得出
而操作数刚好为
2028E - Alice's Adventures in the Rabbit Hole
题意
给定一棵树,皇后想处死爱丽丝,而爱丽丝想逃出去。爱丽丝若在叶子节点则被处死,在根节点则逃出。
每次操作都有
问爱丽丝起点在
做法
先考虑一条链的情况:
假设起点在
则有
即
通项为:
我们在树上做一个短链剖分。
解释一下,就是把树链剖分的重儿子变成到最近叶子的距离最小的儿子。
对于每条短链,我们需要额外算上链顶的父亲。
然后就可以将其当做链上的情况了。
假设当前在
因为当爱丽丝跑到当前链的父亲上时,此时已经换了一条链了,所以需要分开考虑。
则有:
而
于是做完了。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define int ll
#define PII pair<int,int>
#define rep(k,a,b) for (int k = a;k <= b;k++)
#define adde(a,b) v[a].push_back(b)
#define addev(a,b,c) v[a].push_back({b,c});
#define rd read
int read()
{
int f=1,k=0;char c = getchar();
while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
return k*f;
}
const int N = 2e5+5,mod = 998244353;
int n;
int siz[N],top[N],son[N];
int dep[N];
int dis[N];
int fat[N];
int inv[N];
vector<int> v[N];
int ksm(int a,int b)
{
int res= 1;
while (b)
{
if (b & 1) res = 1ll*res * a%mod;
a = 1ll * a * a %mod;
b >>= 1;
}
return res;
}
void dfs1(int x,int fa)
{
fat[x] = fa;
dep[x] = dep[fa] + 1;
for (auto y : v[x])
{
if (y == fa) continue;
dfs1(y,x);
if (!son[x] || dis[son[x]] > dis[y]) son[x] = y;
}
if (son[x])
dis[x] = dis[son[x]] + 1;
}
int f[N];
void dfs2(int x,int tp)
{
top[x] = tp;
int len = dis[tp]+1 + (tp!=1);
int d = dep[x] - dep[tp]+2-(tp==1);d = len-d+1;
f[x] = (d-1)*(ksm(len-1,mod-2))%mod*f[fat[tp]]%mod;
if (x==1)f[x]=1;
if (son[x]) dfs2(son[x],tp);
for (auto y : v[x])
{
if (y == fat[x] || y == son[x]) continue;
dfs2(y,y);
}
}
void solve()
{
cin >> n;
rep(i,1,n-1)
{
int x= rd(),y = rd();
v[x].push_back(y);
v[y].push_back(x);
}
dfs1(1,1);
dfs2(1,1);
rep(i,1,n) cout << f[i] << ' ';
puts("");
rep(i,0,n) v[i].clear(),f[i] = top[i] = siz[i] = dep[i] = dis[i] = fat[i] = son[i] = 0;
}
signed main()
{
rep(i,1,2e5) inv[i] = ksm(i,mod-2);
int t;t = rd();
while(t--)
{
solve();
}
return 0;
}
A. ⚔️
来源:2024 NOIP 模拟赛 Day 12
抽象题目加抽象解法。
笛卡尔树 + 树形 dp
题意
有
会进行
你可以任意选择比赛顺序和相同情况下的胜者,问最后哪些选手可能成为最后的赢家。从小到大输出这些选手的编号。
做法
我们对于原序列建笛卡尔树,首先最大值是一定可以赢的。
然后我们观察到每个节点可以将其子树所有点吃掉。
设选手
于是我们可以列出 dp:
可以得到:
于是
从上往下做就做完了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!