9.week4
这周是高强度写题(启发式练麻了)
派遣
这题的题目稍微有点复杂
所以说我们有一句话题意:
个点组成一棵树,每个点都有一个领导力和费用,可以让一个点当领导,然后在这个点的子树中选择一些费用之和不超过 的点,得到领导的领导力乘选择的点的个数(领导可不被选择)的利润。求利润最大值。 ;
写到一半发现又看错题了
选择一个点有对应的花费,求的是花费不超过
这下就有一个很好的贪心可以发现了:
我们对于一颗子树,如果当前费用和大于$m$了,那就直接弹出大根堆顶然后减值,这不会影响正确性因为这个点肯定是对于父亲子树要弹出的
注意维护堆中元素多少然后去启发式
贴一小段:
void dfs(int x) {
s[x] += w[x];
q[x].push(w[x]);
for (int i = p[x]; i; i = e[i].nt) {
int v = e[i].v;
dfs(v);
if (q[x].size() < q[v].size()) swap(q[x], q[v]), swap(s[x], s[v]);
while (!q[v].empty()) {
q[x].push(q[v].top());
s[x] += q[v].top();
q[v].pop();
}
}
while (s[x] > m) {
s[x] -= q[x].top();
q[x].pop();
}
mmax(q[x].size()*L[x], ans);
}
特技飞行
找性质题
我们发现对于一种动作所有的贡献那就是
那么我们直接贪心地放动作就行
中间有空位怎么办?完全不影响最后答案,可以认为不用填,实际上是填了也没用
逃学的小孩
确定贪心策略然后按题意模拟出答案
策略是:
搜出直径,选择直径的两个端点作为
, ,然后枚举 .
为什么是对的呢?
似乎不是很好证明啊
间歇泉
因为要求第k大的,我们选择二分.
二分一下第k大的是否大于等于我的二分值就行了
然后就是选的问题.
手推一下发现比较值是
明显可以双指针.
就做完了
数列
自己手推一下规律:
⇒
可以递推,所以说拿出递推矩阵
乘上
就
落忆枫音
题目好长,所以说
给定一张有向无环图,这张图满足一个性质:以点1为根节点,保证至少有一棵有向树,连接所有的节点.
现在向这张图中新增一条给定的有向边,求增加这条有向边之后,有向无环图中的有向树的数量.
答案可能会很大,所以要对取模.
首先我们先想DAG的话怎么生成树计数
发现其实就是
但是你现在这么求就是错的,因为加了一条边,这么写出来的会有环.
那我们直接考虑把环减掉
那就要把所以可能出环的方案都统计出来.
那就
由于边是
对于环上点,我们需要为他定向,就是对方案数除以度数.
贴一小段:
dp[y]=all;
void Topo(){
queue<int>q;
F(i,1,n)if(!in[i])q.push(i);
while(!q.empty()){
int u=q.front();q.pop();
dp[u]=dp[u]*Pow(d[u],mod-2)%mod;
for(int i=p[u];i;i=e[i].nt){
int v=e[i].v;
(dp[v]+=dp[u])%=mod;
if(--in[v]==0)q.push(v);
}
}
cout<<(all+mod-dp[x])%mod;
}
还是一个挺强的题的
快速查询
首先,全局加/乘/推平,都是两个变量就可以维护的
单点修改和单点查询呢?
开
然后就做完了.
但是会有一些细节上的问题:
- 维护的是先乘后加,注意修改的时候两个标记之间的影响
- 加入的点应该是去了全局标记后的答案
- 线性预处理阶乘,不然复杂度不对.
这边的阶乘写的是
f[0]=1;
F(i,1,mod-1)f[i]=f[i-1]*i%mod;
ifac[mod-1]=Pow(f[mod-1],mod-2);
inv[mod-1]=Pow(mod-1,mod-2);
for(int i=mod-2;i>=1;--i){
ifac[i]=(i+1)*ifac[i+1]%mod;
inv[i]=f[i-1]*ifac[i]%mod;
}
inv[0]=0;
不够优雅
inv[1]=1;
for (int i=2;i<mod;++i) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
就这些了
Cows in a Skyscraper G
因为
状态两维,分别是分组个数和选择的状态,里面是当前状态最小的电梯存的重量
这里要理解一些东西,就是我对于状态的枚举其实隐含了一个转移顺序,很多的状压
就是说,当我枚举到状态
谈一下这个
- 枚举开了几个电梯了
- 枚举状态
- 枚举现在我要加入的点,作为转移更新
- 我们只考虑这个状态最小的那个电梯是多少,毕竟牛只能放在一个电梯里面.
- 放得下就放,放不下就开一个新的
非线性的状压dp真不熟
Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
启发式又一枪
要求的是最长的重排回文路
还是一如既往,这个求的是异或起来为0或者只有一个1
字符集不大,这部分暴力做也没什么问题
我们可以模仿路径的求法,做出点到根的路径异或.
开
然后就做完了
糖果传递
没写过这个,模拟赛被成功卡死在这个一步.
贪心的思路很简单,但是正确性很不显然,真可恶.
对于这种类型的问题,我们贪心策略如下:
- 先考虑链上的处理,由于第一堆的答案只能由第二堆的来,所以说我们不得不移.
然后所以说要想得到答案,直接对于所有人拥有的糖果- ,然后前缀和,最后 - 我们考虑枚举断点,如果说在k断开,推理一下发现
- 最后就是找出这个最小的
在哪就行了,发现是 是 的中位数时答案最小(考虑尽可能让 )
就这样了.
贴一小段这个的代码:
signed main(){
n=rd();
int s=0;
F(i,1,n)a[i]=rd(),s+=a[i];
ave=s/n;
F(i,1,n)a[i]-=ave;
F(i,1,n)c[i]=c[i-1]+a[i];
int x1=(n+1)/2;
nth_element(c+1,c+x1,c+1+n);
int ans=0;
F(i,1,n)ans+=abs(c[x1]-c[i]);
cout<<ans<<'\n';
return 0;
}
校门外的区间
你可能说线段树板中板,但是你为什么不写ODT呢?到处都是推平操作
ODT快得起飞
稍微说一下细节问题:
- 由于要处理半点,所以说点翻倍
- 特判空集
- 别忘记了插入初始全0集合
- 注意输入的模拟
- 输出的时候要合并区间
线段树写起来还有点麻烦,无论是区间反转还是打印答案.
喜提此题rank1
Cow Exhibition G
还是要点思路的背包
选择智商作为体积,情商作为价值,然后对着做0-1背包
为了反正负数下标,选择平移dp数组,那么值域范围就是
当然统计答案的时候只能用
Lomsat gelral
再来一发启发式
简单来说,就是求子树颜色众数.
都写了这么多次了,这个应该不用多说了吧.
就是注意答案清空和继承的时候不要鲁莽
if(!fl)clear(x),sum=0,mx=0;
其他的时候不要去情况sum和mx,不然就没有继承重儿子了
建造军营
看着他从蓝色变成紫色写完的
首先理解题意,求的是对于一个连通图,选定点作为军营,选定边看守.确保断开任意一条非看守边军营之间仍然联通的方案.
别急,先看部分分.
对于链上版本,我们只要钦定两个点作为军营,那么之间的所有边肯定都要守卫,之间的点直接任意选择.
那么答案可以直接计算.
之后是正解
明显,对于一个边双,怎么断都是联通的,也就是说我们只考虑保卫割边.
我们对边双,都缩起来.
那么图就是一个树了.
剩下的就是
真的不是很好搞.
状态的设计会很大地影响转移和统计答案的难度.
非常菜的我,在这里给出一种比较好的题解的的状态设计:
对于
,指的是u的子树(在合并v)之前没有/有选定的军营
有一些限制:
- 只允许子树内有军营
- 不允许连父向边(否则答案统计在父亲的身上)
我们有比较明显的转移:
初始化
由于子树外的和dcc内的边都是任意是否守卫的,我们可以最后统计答案的时候统计.
以上的所有
要同时转移所以说定临时变量
最后
这个幂次的原理是:
边中只有子树中的边和父向边是已经考虑完了的,也是需要特别考虑的,其他的都是可以随意选不选的,这个正好就是
,但是 时没有父向边,所以说
这样就结束了.
真挺不好搞的.
贴一下关键代码:
void dfs(int x,int ffa){
dp[x][0]=1;
dp[x][1]=pw[siz[x]]-1;
tsiz[x]=1;
for(int i=p[x];i;i=e[i].nt){
int v=e[i].v;if(v==ffa)continue;
dfs(v,x);
tsiz[x]+=tsiz[v];
int t0=dp[x][0]*dp[v][0]*2;
int t1=dp[x][1]*dp[v][0]*2+dp[x][1]*dp[v][1]+dp[x][0]*dp[v][1];
dp[x][0]=t0;
dp[x][1]=t1;
}
int k=tsiz[x];
if(x==1)k-=1;
ans+=dp[x][1]*pw[m-k];
}
Dominant Indices
求的是长度众数
你可以选择再来一发启发式
但是我们可以追求
为什么长剖能
太酷炫了.
这个难度主要在实现,接下来就对着代码讲解吧:
#include<bits/stdc++.h>
#define int long long
#define F(i0,i1,i2) for(int i0=(i1);i0<=(i2);++i0)
using namespace std;
inline int rd() {
int x = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) {if(ch == '-')f = 1;ch = getchar();}
while(isdigit(ch)) {x = x * 10 + ch - 48;ch = getchar();}
return f ? -x : x;
}
const int N = 1e6 + 5, mod = 1e9 + 7;
struct Id {int v, nt;} e[N << 1];
int p[N], id = 1;
void add(int x, int y) {e[++id] = {y, p[x]};p[x] = id;}
int n;
int dep[N], son[N];
void dfs1(int x, int ffa) {
for(int i = p[x]; i; i = e[i].nt) {
int v = e[i].v;if(v == ffa)continue;
dfs1(v, x);
if(dep[son[x]] < dep[v])son[x] = v;//出重儿子
}
dep[x] = dep[son[x]] + 1;//此节点的最大深度为重儿子+1
}
int buf[N];//内存区
int *f[N], *now = buf;//指针定义,动态分配内存
int ans[N];
void dfs2(int x, int ffa) {
f[x][0]=1;//把自己加进去
if(son[x]) {
f[son[x]] = f[x] + 1;//把重儿子的答案桶放在1位偏移的地方
dfs2(son[x], x);
ans[x]=ans[son[x]]+1;//ans[x]更新
}
for(int i = p[x]; i; i = e[i].nt) {
int v = e[i].v;
if(v == ffa || v == son[x])continue;
f[v] = now;//放入内存区
now += dep[v];
dfs2(v,x);
F(i,1,dep[v]){
f[x][i]+=f[v][i-1];//有1偏移的对于桶加点
if(f[x][i]>f[x][ans[x]]||((f[x][i]==f[x][ans[x]])&&(i<ans[x])))ans[x]=i; //ans[x]更新
}
}
if(f[x][ans[x]]==1)ans[x]=0;//如果说这个众数是1,那直接选自己得了
}
signed main() {
n = rd();
F(i, 1, n - 1) {
int x = rd(), y = rd();
add(x, y);add(y, x);
}
dfs1(1, 0);
f[1] = now;
now += dep[1];
dfs2(1, 0);
F(i,1,n)cout<<ans[i]<<'\n';
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话