2022牛客暑期多校第二场
【构造+打表找规律/Dilworth定理】G
【题意】
构造一个 n 的排列,使得 max(lis,lds) 最小。(lis为最长上升子序列长度,lds为最长下降子序列长度)
【题解】
最小值为。
可行性:构造方法为依次排列个上升/下降序列,如 2 1 / 5 4 3 / 8 7 6
正确性:Dilworth定理 - 偏序集的最长反链 = 最小链分划
偏序集:能够定义满足以下条件的关系的集合
- 且
- 且
链:的子集使得,且
最小链划分:将划分成条互不相交的链,使得最小
最长反链:的最大子集,使得
此问题中,表示排列中的第个数,则:
- 关系定义为
- 一条链即为一个上升子序列
- 最长反链即为最长下降子序列
因此,根据 Dilworth 定理,最长下降子序列 = 最小(上升子序列)划分,即 lds = #is。由于 #,那么,那么。
【代码】
#include <bits/stdc++.h> using namespace std; const double eps=1e-9; int T,n,num,cnt,r,idx; int main() { scanf("%d",&T); while (T--){ scanf("%d",&n); num=(int)(sqrt((double)n)-eps)+1; //节数 cnt=n/num; //每节有几个数 r=n%num; //余数放到最后r节 idx=0; for (int i=1;i<=num-r;i++){ idx+=cnt; for (int j=idx;j>idx-cnt;j--) printf("%d ",j); } for (int i=num-r+1;i<=num;i++){ idx+=cnt+1; for (int j=idx;j>idx-cnt-1;j--) printf("%d ",j); } printf("\n"); } return 0; }
【括号串dp】K
【题意】
给定一个长度为 n 的括号串s(不一定合法),求以 s 为子序列的长度为 m 的合法括号串的数量。
【题解】
:长度为 i,与 s 的最长公共子序列长为 j(不要包含至第 j 位,也不要以第 j 位结尾,否则都会重复计算!), 有 k 个左括号的括号串(不一定合法)的个数
四行为平行关系。注意标红的 +1,很重要!!!因为只有当 s[j+1] = ')' 时,填左括号才能保证由 dp[i-1][j][k-1] 转移过来后最长公共子序列长度仍为 j。
初始条件为 dp[1][0/1][0/1],根据 s[0] 赋值。所求答案为 dp[m][n][m/2]。
【代码】
#include <bits/stdc++.h> using namespace std; const int N=205; const int mod=1e9+7; int T,n,m,dp[N][N][N>>1]; char s[N]; int main() { scanf("%d",&T); while (T--){ scanf("%d%d",&n,&m); getchar(); scanf("%s",s+1); dp[1][0][0]=0; dp[1][1][0]=0; if (s[1]=='(') dp[1][0][1]=0,dp[1][1][1]=1; else dp[1][0][1]=1,dp[1][1][1]=0; for (int i=2;i<=m;i++) for (int j=0;j<=min(i,n);j++) for (int k=1;k<=min(i,m/2);k++){ dp[i][j][k]=0; if (j==0){ if (s[1]==')') dp[i][j][k]=dp[i-1][j][k-1]; //只可能填左括号 continue; } //填左括号 if (s[j]=='(') dp[i][j][k]=(dp[i][j][k]+dp[i-1][j-1][k-1])%mod; if (s[j+1]==')'||j==n) dp[i][j][k]=(dp[i][j][k]+dp[i-1][j][k-1])%mod; //j==n特判 //填右括号 if (k>=i-k){ if (s[j]==')') dp[i][j][k]=(dp[i][j][k]+dp[i-1][j-1][k])%mod; if (s[j+1]=='('||j==n) dp[i][j][k]=(dp[i][j][k]+dp[i-1][j][k])%mod; } } printf("%d\n",dp[m][n][m/2]); } return 0; }
【二分+判负环+取log】D
【题意】
给定一张 n 点 m 边的有向图,边权为实数,图中存在边权乘积大于 1 的环。求最大的 w,使得每条边权乘以 w 后,图中不存在边权乘积大于 1 的环。
【题解】
首先容易发现 w 与边权乘积具有单调关系,因此想到二分答案。
check(w) 的功能是判断图中是否存在边权乘积大于 1 的环(在求最长路的过程中存在使路径越来越长的环),这相当于判负环(在求最短路的过程中存在使路径越来越短的环),可以用 SPFA 解决,时间复杂度 。不过由于图不一定连通,单源最长路可能无法覆盖全图。只需对原 SPFA 稍作修改:dis[u] 表示任意点到 u 点的最长路,初始化为 1(自己到自己,根据题意应为 1),并在开始时将全部点加入队列;cnt[u] 表示最长路径经过的边的数量,如果 cnt[u] >= n,说明图中存在边权乘积大于 1 的环(其实这也是连通图判负环的方法之一)。
除此之外,边权乘积可能很大,对此可以进行取 log 操作,变乘为加。
【实现】
对于实数的二分,退出循环条件为 r-l>eps
,其中 eps = 精度要求 / 10。
【代码】
#include <bits/stdc++.h> using namespace std; const int N=1005; int n,m,b,d,cnt[N]; double a,c,l,r,mid,ans,dis[N]; bool book[N]; struct node{ int v; double w; }; vector<node> out[N]; bool check(double k){ queue<int> q; memset(cnt,0,sizeof(cnt)); for (int i=1;i<=n;i++){ dis[i]=0.0; //取log,log(1)=0 q.push(i); //所有点入队 book[i]=1; } while (!q.empty()){ int u=q.front(); q.pop(); book[u]=0; int len=out[u].size(); for (int i=0;i<len;i++){ int v=out[u][i].v; if (dis[u]+out[u][i].w+k>dis[v]){ dis[v]=dis[u]+out[u][i].w+k; cnt[v]=cnt[u]+1; //记录最长路径经过边数 if (cnt[v]>=n) return 0; if (book[v]==0){ q.push(v); book[v]=1; } } } } return 1; } int main() { scanf("%d%d",&n,&m); for (int i=1;i<=m;i++){ scanf("%lf%d%lf%d",&a,&b,&c,&d); out[b].push_back({d,log(c/a)}); } l=0.0;r=1.0;ans=0.0; while (r-l>1e-7){ //精度要求1e-6 mid=(l+r)/2.0; if (check(log(mid))) l=mid,ans=mid; else r=mid; } printf("%.7lf",ans); return 0; }
【图dp】L
【题意】
有 n 张每张 m 个点的有向图,从中选择连续的 w 张,使得从第一张的 1 号点出发,在每张图中要么留在原地,要么走过一条边,然后到达下一张图中同一编号的点,最终到达第 w 张的 m 号点。求最小的 w。
【题解】
不难设计状态 dp[i][j] 表示到达第 i 张图的 j 号点,最晚从第几张图的 1 号点出发。
表示指向的点。注意标红的”-1“,不可以从 dp[i][j'] 转移,因为 dp[i][j'] 中存储的可能是已经在第 i 张图中走过一条边的值。
初始状态即转移式第一行,所求答案为 min{ i - dp[i][m] +1 }。
由于直接存储会爆内存,又发现每次转移只需要 i-1 的状态,于是将第一维滚动掉。
【实现】
滚动数组实现 dp 转移式中的第一行:读入第 i 张图的边之前,设上一张图的 1 号点位 i:dp[!cur][1]=i。
【代码】
#include <bits/stdc++.h> using namespace std; const int M=2e3+5; int n,m,l,u,v,dp[2][M],cur,ans=2147483647; int main() { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++){ dp[!cur][1]=i; //转移式中第一行 for (int j=2;j<=m;j++) dp[cur][j]=dp[!cur][j]; scanf("%d",&l); for (int j=1;j<=l;j++){ scanf("%d%d",&u,&v); dp[cur][v]=max(dp[cur][v],dp[!cur][u]); } if (dp[cur][m]) ans=min(ans,i-dp[cur][m]+1); cur=!cur; } if (ans!=2147483647) printf("%d",ans); else printf("-1"); return 0; }
【矩阵乘法】I
【题意】略。
【题解】
把式子写成矩阵乘法的形式,其实就是 vectorization。写出式子为:
其中为沿行归一化后所得矩阵,维度为。的维度为。,时限3s。
如果按照顺序计算,的复杂度为,的复杂度为,不可行。解决方法根据结合律改变运算顺序,先计算,复杂度为,再计算,复杂度为,就可行了。
【代码】
#include <bits/stdc++.h> using namespace std; int n,k,d; double X[10005][55],Y[10005][55],sum,XtY[55][55],ans[10005][55]; int main() { scanf("%d%d%d",&n,&k,&d); for (int i=1;i<=n;i++){ sum=0.0; for (int j=1;j<=k;j++){ scanf("%lf",&X[i][j]); sum+=X[i][j]*X[i][j]; } sum=sqrt(sum); for (int j=1;j<=k;j++) //沿行归一化 X[i][j]/=sum; } for (int i=1;i<=n;i++) for (int j=1;j<=d;j++) scanf("%lf",&Y[i][j]); for (int i=1;i<=k;i++) //计算Xnorm^T x Y for (int j=1;j<=d;j++) for (int z=1;z<=n;z++) XtY[i][j]+=X[z][i]*Y[z][j]; for (int i=1;i<=n;i++) //计算Xnorm x XtY for (int j=1;j<=d;j++) for (int z=1;z<=k;z++) ans[i][j]+=X[i][z]*XtY[z][j]; for (int i=1;i<=n;i++){ for (int j=1;j<=d;j++) printf("%.8lf ",ans[i][j]); printf("\n"); } return 0; }
【贪心+实现】H
【题意】
楼高 k 层,有 n 个人要上下楼,每个人有起始楼层和目标楼层,电梯限载 m 人,每次下行必须到达第 1 层才能换上行。电梯走一层消耗一单位时间,上下人时间忽略,求从第 1 层出发,送完所有人,并回到第 1 层所需最少时间。
【题解】
由于电梯每次下行都必须下到底层,因此我们希望每次上行到达的最高楼层最小。那么每次上行,优先选目标楼层高的人,每次下行,优先选起始楼层高的人,这样可以最小化每次上行所达最高层。值得一提的是,是否允许中途下电梯对答案没有影响。
【实现】
很奇妙的实现方法,想了好久,直接看代码吧。
【代码】
#include <bits/stdc++.h> using namespace std; const int N=2e5+5; int n,m,k,a,b,cnt1,cnt2,ans1[N],ans2[N],cnt; long long ans; struct node{ int pos,t; bool operator < (const node &x) const{ if (pos!=x.pos) return pos<x.pos; //priority_queue 符号相反 return t>x.t; } }; priority_queue<node> up,down; int main() { scanf("%d%d%d",&n,&m,&k); for (int i=1;i<=n;i++){ scanf("%d%d",&a,&b); if (a<b) up.push({a,-1}),up.push({b,1}); //从上往下扫(上下行都是),所以上端点+1,下端点-1 else down.push({a,1}),down.push({b,-1}); } //cnt为当前楼层有多少人【需要】坐电梯 //cnt1,cnt2分别表示需要上/下行多少趟 while (!up.empty()){ //上行 node u=up.top(); up.pop(); cnt+=u.t; if (cnt>cnt1*m) ans1[++cnt1]=u.pos; //电梯每走一趟会运 m 人(如果需 >= 供),
//如果当前需要坐电梯的人数 > 当前能运的人数,
//那么趟数+1,该趟到达最高层为当前层 } cnt=0; while (!down.empty()){ //下行 node u=down.top(); down.pop(); cnt+=u.t; if (cnt>cnt2*m) ans2[++cnt2]=u.pos; } for (int i=1;i<=max(cnt1,cnt2);i++) //统计答案 ans+=2ll*(max(ans1[i],ans2[i])-1ll); //每趟到达的最高层为上下行所达最高层取max printf("%lld",ans); return 0; }
【容斥+多项式】E
TBC.
【Trie+线段树+倍增】F
TBC.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构