9.week4

这周是高强度写题(启发式练麻了)

派遣

这题的题目稍微有点复杂
所以说我们有一句话题意:

n个点组成一棵树,每个点都有一个领导力和费用,可以让一个点当领导,然后在这个点的子树中选择一些费用之和不超过m的点,得到领导的领导力乘选择的点的个数(领导可不被选择)的利润。求利润最大值。 n100000

写到一半发现又看错题了

选择一个点有对应的花费,求的是花费不超过m的时候人的最大值。
这下就有一个很好的贪心可以发现了:

我们对于一颗子树,如果当前费用和大于$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);
}

特技飞行

找性质题
我们发现对于一种动作所有的贡献那就是c[i]()
那么我们直接贪心地放动作就行
中间有空位怎么办?完全不影响最后答案,可以认为不用填,实际上是填了也没用

逃学的小孩

确定贪心策略然后按题意模拟出答案

策略是:

搜出直径,选择直径的两个端点作为A,B,然后枚举C.

为什么是对的呢?
似乎不是很好证明啊

间歇泉

因为要求第k大的,我们选择二分.
二分一下第k大的是否大于等于我的二分值就行了
然后就是选的问题.
手推一下发现比较值是(a[i]mid)c[i],只要两个点加起来是大于等于0的就是一个合法方案.
明显可以双指针.
就做完了

数列

自己手推一下规律:

a1,a2,a+b(d)

a1a+a2b+c(a1),a1+d2,d2

可以递推,所以说拿出递推矩阵

[aa00bb000a+ba+b0cc01]

乘上

[1(a1)2(a2)11]

ok

落忆枫音

题目好长,所以说

给定一张有向无环图,这张图满足一个性质:以点1为根节点,保证至少有一棵有向树,连接所有的节点.
现在向这张图中新增一条给定的有向边,求增加这条有向边之后,有向无环图中的有向树的数量.
答案可能会很大,所以要对1e9+7取模.

首先我们先想DAG的话怎么生成树计数
发现其实就是i=1nin[i],还是挺好理解的
但是你现在这么求就是错的,因为加了一条边,这么写出来的会有环.
那我们直接考虑把环减掉
那就要把所以可能出环的方案都统计出来.
那就DAGdp试试
由于边是st的,所以说我们把t作为dp的起点,然后作为终点,所有继承到s身上的方案都是环上方案.
对于环上点,我们需要为他定向,就是对方案数除以度数.
贴一小段:

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;
}

还是一个挺强的题的

快速查询

1e7的询问
首先,全局加/乘/推平,都是两个变量就可以维护的
单点修改和单点查询呢?
multiset
然后就做完了.
但是会有一些细节上的问题:

  • 维护的是先乘后加,注意修改的时候两个标记之间的影响
  • 加入的点应该是去了全局标记后的答案
  • 线性预处理阶乘,不然复杂度不对.
    这边的阶乘写的是
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

因为n很小,所以说你可以状压
状态两维,分别是分组个数和选择的状态,里面是当前状态最小的电梯存的重量
这里要理解一些东西,就是我对于状态的枚举其实隐含了一个转移顺序,很多的状压DP都有,但是以前没有认真考虑到
就是说,当我枚举到状态sta,可以转移到sta的所有子状态已经被当前答案更新

谈一下这个DP的转移过程:

  1. 枚举开了几个电梯了
  2. 枚举状态
  3. 枚举现在我要加入的点,作为转移更新
    • 我们只考虑这个状态最小的那个电梯是多少,毕竟牛只能放在一个电梯里面.
    • 放得下就放,放不下就开一个新的

非线性的状压dp真不熟

Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths

启发式又一枪
要求的是最长的重排回文路
还是一如既往,这个求的是异或起来为0或者只有一个1
字符集不大,这部分暴力做也没什么问题
我们可以模仿路径的求法,做出点到根的路径异或.
1<<22的桶,记录一下每种状态最大深度.
然后就做完了

糖果传递

没写过这个,模拟赛被成功卡死在这个一步.
贪心的思路很简单,但是正确性很不显然,真可恶.
对于这种类型的问题,我们贪心策略如下:

  1. 先考虑链上的处理,由于第一堆的答案只能由第二堆的来,所以说我们不得不移.
    然后所以说要想得到答案,直接对于所有人拥有的糖果-ave,然后前缀和,最后ans=i=1n|S[i]|
  2. 我们考虑枚举断点,如果说在k断开,推理一下发现
    ans=i=1N|S[i]S[k]|
  3. 最后就是找出这个最小的k在哪就行了,发现是S[k]S的中位数时答案最小(考虑尽可能让S[i]S[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快得起飞

稍微说一下细节问题:

  1. 由于要处理半点,所以说点翻倍
  2. 特判空集
  3. 别忘记了插入初始全0集合
  4. 注意输入的模拟
  5. 输出的时候要合并区间

线段树写起来还有点麻烦,无论是区间反转还是打印答案.
喜提此题rank1

Cow Exhibition G

还是要点思路的背包
选择智商作为体积,情商作为价值,然后对着做0-1背包
为了反正负数下标,选择平移dp数组,那么值域范围就是08e5
当然统计答案的时候只能用4e58e5

Lomsat gelral

再来一发启发式
简单来说,就是求子树颜色众数.
都写了这么多次了,这个应该不用多说了吧.
就是注意答案清空和继承的时候不要鲁莽
if(!fl)clear(x),sum=0,mx=0;
其他的时候不要去情况sum和mx,不然就没有继承重儿子了

建造军营

看着他从蓝色变成紫色写完的
首先理解题意,求的是对于一个连通图,选定点作为军营,选定边看守.确保断开任意一条非看守边军营之间仍然联通的方案.
别急,先看部分分.
对于链上版本,我们只要钦定两个点作为军营,那么之间的所有边肯定都要守卫,之间的点直接任意选择.
那么答案可以直接计算.

之后是正解
明显,对于一个边双,怎么断都是联通的,也就是说我们只考虑保卫割边.
我们对边双,都缩起来.
那么图就是一个树了.
剩下的就是dp了.
真的不是很好搞.
状态的设计会很大地影响转移和统计答案的难度.
非常菜的我,在这里给出一种比较好的题解的的状态设计:

对于 dp[u][0/1],指的是u的子树(在合并v)之前没有/有选定的军营

有一些限制:

  1. 只允许子树内有军营
  2. 不允许连父向边(否则答案统计在父亲的身上)

我们有比较明显的转移:
初始化 dpu,0=1,dpu,1=2siz[u]1()
由于子树外的和dcc内的边都是任意是否守卫的,我们可以最后统计答案的时候统计.

dpu,0=dpu,0dpv,02dpu,1=dpu,0dpv,1dpu,1=dpu,1dpv,02dpu,1=dpu,1dpv,1

以上的所有2都是因为这个对应的边可选可不选.
要同时转移所以说定临时变量

最后 ans+=dpu,12(m(tsiz[u][u==1]))
这个幂次的原理是:

边中只有子树中的边和父向边是已经考虑完了的,也是需要特别考虑的,其他的都是可以随意选不选的,这个正好就是tsiz[u],但是u==1时没有父向边,所以说1

这样就结束了.
真挺不好搞的.
贴一下关键代码:

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

求的是长度众数
你可以选择再来一发启发式
但是我们可以追求O(n)的长剖
为什么长剖能O(n)?因为能用妙妙指针技巧O(1)继承重儿子答案.
太酷炫了.
这个难度主要在实现,接下来就对着代码讲解吧:

#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;
}

终于写完了.....

posted @   ussumer  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示