郑州寄 8.8

前言

业精于勤荒于嬉,行成于思毁于随

正文(模拟赛)

卦象:平

感受:T1 口胡,花费 3min,代码写了 40min,难受。写完 T1 对着 T2 发了 20min 的呆,感觉正解需要整点数据结构,但没想明白怎么维护。所以只打了 \(80pts\),大概在 10:00 左右写完 T2。开 T3、T4,显然是不会 T3 正解,很像以前的一道拉插题目,但是拉插早就还给 shr 了,看来需要加训。不过那个拉插题目也有朴素 DP 做法,然而这个模拟赛题没想清楚。试图开 T4,以为是 DAG 上背包的板子题,没曾想,背包容量高达 \(10^9\)。这就很像高效进阶上的深搜题了,再结合 \(n \le 30\),hhh,这辈子不可能写这种东西的(甚至还是多重背包)。因此,回去看 T3,还是不会。被迫去想 T2 正解,不会不会不会,啥都不会。此时云落就像那个 oi 重开模拟器里的红温和破防 buff,啥也不会,咋办???再加上今天这个空调开的忽冷忽热的,写一会代码就想去厕所,而一去厕所又会受到一套魔法伤害,人麻了。T3 和 T4 的部分分还是太有生活了,我都需要值域相关的部分分,结果部分分设计全是 \(n \le ???\) 的形式,难绷。哎,今天又双叒叕是补题压力巨大的一天。明明感觉就是 CSP-S 难度的模拟赛,为啥就是不会呢……

T1

签,因码字速度慢耗费将近一个点

题意

有一个 \(n\)\(m\) 列的网格。每个网格上已经有一个人,或者是空的,没有人。且保证至少还有一个位置没有人。现在梅需要选择一个空位,并且移动到那个空位上。

梅落座后,死灵法师会随机让一个人在座的人(包括梅)感染瘟疫,在接下来的每一天中,感染瘟疫的人会向四联通的人传染瘟疫(即向上,下,左,右与其直接相连的四个人传染瘟疫)

梅想要最小化自己感染瘟疫的概率,请告诉她如果她以最优的方式选择空位,感染瘟疫的概率最小是多少

题解

简单维护连通块大小,难度橙

代码

不贴了

T2

想到了一些比较简单 \(80pts\) 做法,也猜到了调和级数复杂度,但没想到暴力跳跃就是正解,真是(消音)的菜完了

题意

数轴上分布着位置两两不同的 \(n\) 个点,第 \(i\) 个点的位置是 \(a_i\)

定义共振效果:如果一个点被标记,那么距离 \(k\) 以内的所有点都会在这一秒被标记(共振无法触发连锁反应)

你可以标记任意一个点,接下来引发共振效果。对于所有标记初始点的方案,标记所有点的时间的最小值为答案

你需要对所有的 \(1 \le k \le 10^6\) 求出答案

题解

结论:从最左端的点开始向后跳跃,跳跃步数折半再上取整就是答案

考虑预处理 ans[k],显然可以暴力跳跃,时间复杂度是调和级数的

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m,a[N],nxt[N],ans[N];
int main(){
    freopen("beacon.in","r",stdin);
    freopen("beacon.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    int l=N,r=0;
    for(int i=1,x;i<=n;i++){
        cin>>x;nxt[x]=x;a[i]=x;
        l=min(l,x),r=max(r,x);
    }
    int mx=0;
    for(int i=2;i<=n;i++)mx=max(mx,a[i]-a[i-1]);

    for(int i=l;i<=r;i++)
        if(!nxt[i])nxt[i]=nxt[i-1];
    
    for(int x=mx;x<N;x++){
        int i=l;
        while(i<r){
            i=nxt[min(i+x,r)];
            ans[x]++;
        }
    }

    for(int i=1,x;i<=m;i++){
        cin>>x;
        if(x<mx)cout<<-1<<' ';
        else cout<<(ans[x]+1)/2<<' ';
    }
    cout<<'\n';

    return 0;
}

T3

老老实实学 划艇 的朴素 DP 做法

题意

image

题解

可恶的实数,我们需要几何概型!

容易发现所有可能的区间,与目标区间是两个相同的子问题,所以做完分母做分子就行了

然后容易想到把区间端点离散化,第 \(x\) 小的记为 \(b_x\),跑 DP

而 DP 是简单的,记 \(f_{x,i}\) 表示前 \(x\) 个区间扔了 \(i\) 个点的方案数(几何意义下的)

刷表转移

首先区间 \([b_x,b_{x+1}]\) 如果不放,那么有

\[f_{x,i} \to f_{x+1,j} \]

否则,枚举放了 \(j-i\) 个点,那么有

\[\frac{f_{x,i} \times (b_{x+1}-b_x)^{j-i}}{(j-i)!} \to f_{x+1,j} \]

解释一下,分子上面乘的系数表示可以在区间内随机撒点,而分母则是定序,因为在所有的随机撒点方案中只有一种是符合大小关系的

代码

记得初始化!记得清空!!!

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=505,MOD=1e9+7;
int n,d,a[N],l[N],r[N];
int fac[N],ifac[N];
int b[N<<1],c,f[N<<1][N],p,q;
inline void chkmx(int &x,int y){x=max(x,y);return;}
inline void chkmn(int &x,int y){x=min(x,y);return;}
inline void ADD(int &x,int y){
    x%=MOD;y%=MOD;x=(x+y)%MOD;
    return;
}
inline int qpow(int a,int b){
    int res=1;
    while(b){
        if(b&1)res=res*a%MOD;
        a=a*a%MOD;b>>=1;
    }
    return res;
}
inline int work(){
    memset(b,0,sizeof(b));c=0;
    memset(f,0,sizeof(f));
    for(int i=1;i<=n;i++)b[++c]=l[i],b[++c]=r[i];
    sort(b+1,b+c+1);c=unique(b+1,b+c+1)-b-1;
    f[1][0]=1;
    for(int x=1;x<=c-1;x++)
        for(int i=0;i<=n;i++){
            ADD(f[x+1][i],f[x][i]);
            int pw=1,len=b[x+1]-b[x];
            for(int j=i+1;j<=n;j++){
                if(b[x]<l[j]||b[x+1]>r[j])break;
                pw=(pw*len)%MOD;
                ADD(f[x+1][j],f[x][i]*(pw*ifac[j-i]%MOD)%MOD);
            }
        }
    return f[c][n];
}
inline void init(){
    fac[0]=1;
    for(int i=1;i<N;i++)fac[i]=(fac[i-1]*i)%MOD;
    ifac[0]=1;
    for(int i=1;i<N;i++)ifac[i]=qpow(fac[i],MOD-2)%MOD;
    return;
}
signed main(){
    freopen("wizard.in","r",stdin);
    freopen("wizard.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    init();
    
    // for(int i=1;i<=10;i++)cerr<<fac[i]<<' '<<ifac[i]<<endl;

    cin>>n>>d;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)cin>>l[i]>>r[i];
    q=work();
    for(int i=1;i<=n;i++)chkmx(l[i],a[i]-d),chkmn(r[i],a[i]+d);
    p=work();

    // cerr<<p<<' '<<q<<endl;

    cout<<p*qpow(q,MOD-2)%MOD<<'\n';
    return 0;
}

T4

可恶的神秘复杂度题

题意

给定一个无重边无自环 DAG,结点 \(i\) 上存在 \(k_i\) 个价值为 \(v_i\),重量为 \(w_i\) 的物品,求在所有 \(1 \to n\) 的路径方案中,在背包容量 \(r\) 范围内获得物品价值最大的,输出最大价值,不可达则 \(-1\)

注意看数据范围!!!

\(n \le 30 ,\ m \le \frac{n(n+1)}{2} ,\ 1 \le r \le 10^9 ,\ 1 \le w_i \le 5 ,\ 1 \le v_i,k_i \le 10^9\)

题解

结论:对于物品 \(i\),只需要保留 \(w_i\) 个参与背包计算,剩余的部分按照性价比(即 \(\frac{v_i}{w_i}\))排序后贪心即可

证明先挖个坑,以后填

对于链的部分分,可以直接按照上面的做,时间复杂度 \(O(n^2 \times c^2)\)

而在 DAG 上,我们容易想到枚举经过点的子集,时间复杂度 \(O(\text{不能过})\)

然后我们发现,如果子集之间存在真包含关系,那么集合大小更、较大的在答案上的贡献一定不劣于集合大小较小的

进一步地,我们可以进行一个“反向松弛”的操作

对于一条边 \((u,v)\),如果存在一条不经过 \((u,v)\)\(u \to v\) 的路径,那么 \((u,v)\) 则是可以删除的。删除该边的依据就是上面子集包含关系的分析

然后,题解表示,剪枝后的图可以通过

(具体地,看 这里 叭!)

代码

这就是输出 \(-1\) 的魅力吗?

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define vi vector<int>
#define pb push_back
#define ppb pop_back
using namespace std;
const int N=35,M=1e3+5;
int n,m,r;bool G[N][N],vis[N];
struct node{int w,v,k;}a[N];
int head[N],tot;
struct Edge{int to,nxt;}e[N*N];
vi o,p;int f[M],ans=-1;
inline void add(int u,int v){
    e[++tot]={v,head[u]};head[u]=tot;
    return;
}
inline void judge(int S,int T){
    for(int i=1;i<=n;i++)vis[i]=false;
    G[S][T]=false;
    queue<int> q;q.push(S);vis[S]=true;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int v=1;v<=n;v++){
            if(!G[u][v]||vis[v])continue;
            q.push(v);vis[v]=true;
        }
    }
    G[S][T]=true;
    if(!vis[T])add(S,T);
    return;
}
inline bool cmp(int x,int y){
    return a[x].v*a[y].w>a[y].v*a[x].w;
}
inline void cal(){
    p=o;sort(p.begin(),p.end(),cmp);
    memset(f,0,sizeof(f));
    // DP
    int sum=0;
    for(int i:p){
        int t=min(a[i].k,5ll);
        sum+=t*a[i].w;
        for(int j=sum;j>=0;j--)
            for(int k=1;k<=t&&k*a[i].w<=j;k++)
                f[j]=max(f[j],f[j-k*a[i].w]+k*a[i].v);
    }
    // Greed
    for(int j=0;j<=min(sum,r);j++){
        int rem=r-j,res=f[j];
        for(int i:p){
            int k=min(max(a[i].k-5ll,0ll),rem/a[i].w);
            rem-=k*a[i].w,res+=k*a[i].v;
        }
        ans=max(ans,res);
    }
    return;
}
inline void dfs(int u){
    o.pb(u);
    if(u==n)cal();
    for(int i=head[u];i;i=e[i].nxt)dfs(e[i].to);
    o.ppb();
    return;
}
signed main(){
    freopen("rider.in","r",stdin);
    freopen("rider.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m>>r;
    for(int i=1;i<=n;i++)cin>>a[i].w>>a[i].v>>a[i].k;
    for(int i=1,u,v;i<=m;i++)cin>>u>>v,G[u][v]=true;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(G[i][j])judge(i,j);
    dfs(1);
    cout<<ans<<'\n';
    return 0;
}

小结

正文(加餐)

线段树 \(+\) 离线算法(不是哥们?)选讲,难绷

关键词:势能分析、标记下放、区间合并、分治

题呢?题呢?题呢?

一下午的线段树,标记下放;一晚上的 \(k\) 维偏序、离线扫描线

后记

世界孤立我任它奚落

完结撒花!

posted @ 2025-08-08 16:12  sunxuhetai  阅读(8)  评论(0)    收藏  举报