本场金牌为超低罚时六题,稳拿金牌需要做出第七题。
但是我只会六题,这里是前六题的题解。
签到但不是完全签到,需要讲。
首先每个位置只会走一次,所以让 ai 加一的操作只会在第一次到达某个位置时连续施行。
让 ai 加一再跳转需要花费一个时间,让 ai 加二再跳转需要花费两个时间,你可以理解成先走到 i+ai 的位置,再花费 1 的时间往后走一个位置,也可以花费 2 的时间往后走两个位置。所以我们把每一个位置往下一个位置连花费为1的边就好了。
但这个连边是需要一个前提的,我们得能够先走到 i 的位置,这些向后连的边才有意义。比如终点是1,但是 a0=2,你是不能一步到达的。
解决方案很简单,不要把0当初始点,而是把 a0 当初始点,因为走到终点是至少需要跳这一步的。
一道很像网络流的矩阵选数题。题面中的 NP-complete 把我队友给误导了,其实解法很简单。
和之前一道类似的矩阵构造题很像,先全部改成一致颜色,然后只需要考虑一个方向。
这题的解法也是类似,先把所有的1改成0,之后修改任意位置其实都是加一。为了同时满足行和列的要求,有一个贪心的选数方法,比较经典。
当你的重置能力CD好了之后,你有两个选择:一是等你的普通攻击CD,CD好了之后先攻击,再重置,然后立即攻击;二是马上重置,然后攻击。
其他的选择,你手玩一下会发现是愚昧的。
而这两个选择结果都是相同的,都会使得两个技能重新开始冷却(其实就和重新开始一个情况了)。
这两种选择,周期不一样,攻击次数不一样。
数据范围允许我们枚举第二种选择的数量,然后推算出第一种选择的数量,剩余部分一直平A。
每个点入度上限为3,简单多了。
首先一个点不能连三个红边,也不能连三个蓝边,所以红边的连通块只能是链或环。
然后发现连成环也允许,一个红色环至少需要三个点,这些点无法通过蓝边连起来。
所以蓝色连通块和红色连通块都是链。
然后发现,链的长度还不能超过四,也就是说最多四个点,因为一个点想要成为红点的链中意味着它要连两个红边,只能连一个蓝边,它就是蓝边的链头或链尾,我们不允许三个链头链尾出现,所以红链只能有两个链中,链长度最多是四。
然后就是分类讨论,一个点,两个点,三个点,四个点。四个情况。
妙妙树上DP,我写的这个是一个很奇怪的做法。
首先要发现一点:虽然车是按从小到大一个一个寻找空位的,但我们不需要关心它们的具体顺序,只要是一个合法的方案,我们调换任意两辆车的顺序,依旧是合法的。
所以我们dp时就不需要考虑车的编号了,只需要考虑每个点上有几辆车。
最后的答案如何统计,假如第 i 个点停了 bi 辆车,那么这样的方案对应到车辆编号的情况数就有 n!b1!b2!...bn! 种,也就是超排列。我们不能让最后的方案数直接乘以 n! 就是因为有些车是在同一个点的,直接乘以 n! 会重复计算这一部分。我们 dp 出来的方案数不好统计究竟有多少车在同一个点,但我们发现可以把 bi! 分母的这部分先计入我们的dp,也就是说,只要选了一个大小为 bi 的点(bi 辆车在这个点),我们就让 dp 结果除以 bi,最后再乘上一个 n! 就是最终答案了。
dp 方程怎么设,怎么转移,需要观察题目的性质。
因为题目中的过程所有车都是根向走的,我们将时间倒流,每个点上有一辆车,所有车往叶向走回到初始时刻,你会发现一个性质,每一棵子树中车的数量一定不小于子树大小,而且多出来的部分也不会超过这棵子树到根的距离。题目所给的 “随机数据” 的性质就在这里体现了,每个点到根的距离期望值不会超过log。
我们设 f[u][i] 表示 u 这棵子树中车比点多了 i 个的方案数,儿子们多出来的点数加起来就是父亲多出来的点数,所以这是一个累加求和的背包问题。合并完之后我们要选择 u 这个父亲结点上车的数量,这时我们要让答案除以 bu,具体含义就是上一段所讲的内容。
这题我们捏了个很有趣的东西哈哈,我们称之为 “左偏笛卡尔树”。
我们的思路是酱紫的:
首先看字典序最小的拓扑排序,我们找到最大的那个数字,连一条边让它指向右边的整体,表示原图中肯定是先访问了这个点才能访问右边的那些点,否则这个数字就不会出现在这里。
而它左边的哪些数字呢,我们把它们视作并列的兄弟关系,因为即使它们之间相互不连边,在最小拓扑排序中数大的点依旧是后访问。
因此我们可以递归地构建一棵树:找到区间中最大的数,左边的整体成为它的兄弟,右边的整体成为它的儿子。
左右区间各找最大点成为儿子,这样构造的树是标准的笛卡尔树,而我们这个是右边成为儿子,左边成为兄弟,我们形象地称之为 “左偏笛卡尔树”。
然后是最大的拓扑排序,原理是一样的,我们继续构造一棵树,把两棵树的所有边加在一起,就是答案的图。(这里要注意第二棵树的边往第一棵树里加时,不允许出现右边的点连向左边这种情况,可以用第一棵树的dfn序来判断,若有这种边直接不合法)
正确性怎么证明呢?
首先是必要性:想要得到题目所给的最小最大拓扑序,我们必须存在这些边。因为我们建图就是为了满足这样的性质,有这些边才能导致这样的顺序。
然后是充分性:只要我们有了这两棵树的所有边,我们就能得到题目所给的最小最大拓扑序。因为在任意一棵树种不存在右侧连向左侧的边,且兄弟结点从左到右依次增大,我们总是会先访问左侧的点,然后是左侧点的儿子(它们比父亲结点优先级更高,因而肯定也要比父亲的右兄弟优先级高),进而访问右侧点,因此一定会得到题目所给的顺序。
证毕。
代码实现:递归造树,RMQ或线段树查询区间最大最小值。
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define FOR() int le=e[u].size();for(int i=0;i<le;i++)
#define QWQ cout<<"QwQ\n";
#define ll long long
#include <vector>
#include <queue>
#include <map>
#define ls now<<1
#define rs now<<1|1
using namespace std;
const int N=801010;
const int qwq=303030;
const int inf=0x3f3f3f3f;
int T;
int n,m;
int a[N],b[N];
struct E{
int mx,mi,idx,idi;
}t[N<<2],ling;
vector <int> e[N];
int du[N];
int vis[N];
int st1[N],st2[N],cnt;
int dfn[N],tim;
inline int read() {
int sum = 0, ff = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * ff;
}
void add(int u,int v) {
e[u].push_back(v);
du[v]++;
}
E pushup(E A,E B) {
E C = ling;
if(A.mx > B.mx) C.mx = A.mx, C.idx = A.idx;
else C.mx = B.mx, C.idx = B.idx;
if(A.mi < B.mi) C.mi = A.mi, C.idi = A.idi;
else C.mi = B.mi, C.idi = B.idi;
return C;
}
void built(int now,int l,int r) {
if(l==r) { t[now] = {a[l],a[l],l,l}; return ; }
int mid = l+r >> 1;
built(ls, l, mid);
built(rs, mid+1, r);
t[now] = pushup(t[ls],t[rs]);
}
E query(int now,int l,int r,int x,int y) {
if(x<=l && r<=y) return t[now];
E res = ling;
int mid = l+r >> 1;
if(x<=mid) res = pushup( query(ls, l, mid, x, y), res );
if(y>mid) res = pushup( query(rs, mid+1, r, x, y), res );
return res;
}
void solve(int fa,int l,int r,int cl) {
if(l>r) return ;
E wo = query(1, 1, n, l, r);
if(cl==1) {
add(fa,wo.mx);
solve(wo.mx, wo.idx+1, r, cl);
solve(fa, l, wo.idx-1, cl);
}
else {
if(dfn[wo.mi]<dfn[fa]) { cout<<"No\n"; exit(0); }
add(fa,wo.mi);
solve(wo.mi, wo.idi+1, r, cl);
solve(fa, l, wo.idi-1, cl);
}
}
void DFS(int u) {
vis[u] = 1;
FOR() {
int v = e[u][i];
if(vis[v]) continue;
du[v]--;
if(!du[v]) DFS(v);
}
}
void TREE(int u) {
dfn[u] = ++tim;
for(int i=e[u].size()-1;i>=0;i--) {
TREE(e[u][i]);
}
}
int main() {
ling = {-inf,inf,0,0};
n = read();
for(int i=1;i<=n;i++) {
a[i] = read();
}
built(1, 1, n);
solve(0, 1, n, 1);
TREE(0);
for(int i=1;i<=n;i++) {
a[i] = read();
}
built(1, 1, n);
solve(0, 1, n, 2);
for(int i=0;i<=n;i++) {
if(!du[i] && !vis[i]) DFS(i);
}
for(int i=1;i<=n;i++) if(!vis[i]) {cout<<"No\n"; return 0;}
for(int i=1;i<=n;i++) {
for(int v : e[i]) {
st1[++cnt] = i; st2[cnt] = v;
}
}
cout<<"Yes\n";
cout<<cnt<<"\n";
for(int i=1;i<=cnt;i++) cout<<st1[i]<<" "<<st2[i]<<endl;
return 0;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具