杂项
模拟赛和平时做题时不时有一些没见过的东西,写一下。
斯特林数
讲递推和初步性质。退役前可能学不到多项式。
第一类斯特林数:
从 dp 意义上推递推公式,设
初始化
这个东西还可以用来推上升幂下降幂相关,是多项式的内容,提一嘴。
其中
排个序从中抽若干个点
第二类斯特林数:
然后捯饬一下可以解决一些小问题比如
然后
然后这个就是斯特林展开即
模拟赛考的是树上全点对路径长
然后
然后换个根就是
统计一下就好了。
斯坦纳树
以下内容出自我的题解。
这个题就是最小斯坦纳树的板题
考虑一个考场一眼想到的错解:跑最小生成树然后跑它的极小连通子树,错点在于最小生成树为了全点集的最优化而一定程度舍弃关键点集的最优化,进而枚举可行的点集一直跑最小生成树即可保证正确性。
现在考虑解决复杂度问题,能想到上文的错解建立在潜意识中的一个正确认知:答案的子图一定是一棵树。证明是容易的,边权全为正,对已有的一个联通关键点的树进行加边则一定不优。
再看树性质对求答案有何帮助,可以钦定一个关键点子集
现在设
对于前者,提供了一种转移方式:考虑给树添加一个
另外,如果
对于后者,认为
#include<bits/stdc++.h>
#define int long long
#define MAXN 105
#define MAXM 505
#define N (1<<10)+5
using namespace std;
int n,m,k;
struct node{
int v,w,nxt;
}edge[MAXM<<1];
int h[MAXN],tmp;
inline void add(int u,int v,int w){
edge[++tmp]=(node){v,w,h[u]};
h[u]=tmp;
}
struct point{
int u,w;
bool operator <(const point &x)const{
return w>x.w;
}
};
priority_queue<point>Q;
int dp[MAXN][N];
bool vis[MAXN];
inline void dijkstra(int S){
memset(vis,0,sizeof(vis));
while(!Q.empty()){
point now=Q.top();
Q.pop();
int u=now.u;
for(int i=h[u];i;i=edge[i].nxt){
int v=edge[i].v,w=edge[i].w;
if(dp[u][S]+w<dp[v][S]){
dp[v][S]=dp[u][S]+w;
Q.push((point){v,dp[v][S]});
}
}
}
}
int inS[MAXN];
const int inf=1e18;
signed main(){
freopen("steiner.in","r",stdin);
freopen("steiner.out","w",stdout);
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1,u,v,w;i<=m;i++){
scanf("%lld%lld%lld",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
int toT=(1<<k)-1;
for(int S=0;S<=toT;S++)for(int i=1;i<=n;i++)dp[i][S]=inf;
for(int i=1,u;i<=k;i++){
scanf("%lld",&inS[i]);
dp[inS[i]][1<<(i-1)]=0;
}
for(int S=1;S<=toT;S++){
for(int i=1;i<=n;i++){
for(int s=S;s;s=(s-1)&S)dp[i][S]=min(dp[i][S],dp[i][s]+dp[i][S^s]);
if(dp[i][S]==inf)continue;
Q.push((point){i,dp[i][S]});
}
dijkstra(S);
}
printf("%lld",dp[inS[1]][toT]);
return 0;
}
时间复杂度
然对于斯坦纳树相关问题可以进行一定扩展比如这道题。
问题变为给定几个关键点群求出它们的斯坦纳森林。
不同颜色的关键点集间可以有共同边,跑若干次斯坦纳树的做法显然错误。
可以设
进而考虑对一次斯坦纳树模版中
最后
代码部分则是将模版中 dp 部分做上文修改。
for(int S=1;S<=toT;S++){
for(int i=1;i<=n;i++){
for(int s=S;s;s=(s-1)&S)dp[i][S]=min(dp[i][S],dp[i][s]+dp[i][S^s]);
if(dp[i][S]==inf)continue;
Q.push((point){i,dp[i][S]});
}
dijkstra(S);
bool f=1;
for(int i=1;i<=k;i++){
if(!ned[i])continue;
f&=!((S&ned[i])&&((S&ned[i])!=ned[i]));
}
if(!f)continue;
for(int i=1;i<=n;i++)ans[S]=min(ans[S],dp[i][S]);
}
for(int S=1;S<=toT;S++)
for(int s=S;s;s=(s-1)&S)
ans[S]=min(ans[S],ans[s]+ans[S^s]);
printf("%lld",ans[toT]);
在另一道题:
进行最短路松弛操作过程中可以顺带记录更新来源,最后输出方案。另外这道题的 dp 过程由于是点权所以稍有不同,详见luogu题解。
贪心: johnson 不等式 和 knuth 洗牌算法
在贪心单子里学到的神秘技巧,作记录。
给出三倍经验并说明题目三。先提一嘴 min-max容斥:
比如题目三的一组先后加工的
然后进行交换
后者比前者更优满足
然后没了,上面的两道题就是把推导过程复杂化。
但是直接这么写comp函数是有问题的,原因是stl的排序函数需要满足该式子不满足的一些性质,不会证,见luogu第二篇题解。
总之就是这样的排序在先前序列顺序不同的情况下会出现不同的答案,所以有神人提出不断对序列打乱排序若干次后得到正确答案。然后就要引进knuth洗牌算法。
但是我太懒了所以给个链接。
平方的神奇转化
为一碟醋包盘饺子。给这样一道题,求所有给定长度的
发现平方的存在根本无法 dp。然后就要把平方拆了。以下内容粘自我的题解。
考虑一个比较牛逼的转化:
则答案中
对于前者可以直接进行传统的计数 dp。对于后者,为了去重在统计第
现在考虑如何实现计数dp。
挖掘性质:序列第
对第
就是要么
这个计数过程就可以直接提供答案
同前文所说的,令对的第二个元素在
这一部分的贡献
加起来对每一位
和哈希
我觉得不需要指名道姓哪道题了...前两天模拟赛的一题也可以用这个技巧。难点在于想到要用这个东西,问题是能拿来练的题根本找不到,所以很难想到要用。
实现没有难点,得分基本看脸。但是我脸黑,为了避免不必要的挂分,用这道不用指名道姓的题提供一个提高正确性的处理。
那么和hash就是把难以表示但容易拆解为公共特征的特殊状态用这个公共特征的hash值表示出来。比如星战里每个点一条出度的随机化权值和应当是一个固定且恰好可以表示答案状态的数,但如果就是冲突了就会很傻逼。进而考虑对某一个元素的权值赋值为其余所有元素随机化权值和的相反数,这样一个答案的权值一定是0,操作空间很大。
然后看模拟赛这道题。给定一个长度
就可以把
但你妈就是被卡了,卡正确性卡速度,负优化,可以按上面的方式把一个权值设成其他权值和的相反数。那一段合法区间的权值一定是0了,把权值和排个序,相同段两两取个答案,
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律