2024暑假集训测试23

前言

image

T2 部分分给得特别足,\(60pts\),而且他不可能剩下的数据全放菊花,所以得到了 \(76pts\),但赛时想了很长时间正解,没有想出来,给后面题剩的时间不多,就都胡暴力了,\(T4\) 甚至忘了剪枝,剪完之后 \(20pts\to 60pts\) 没绷住。

说到这儿要吐槽一下 T4 数据水的离谱,什么高级复杂度的都放过去了(当然纯爆搜没放过去),在 CF 上第六个点就 T 了。

赛时有人 T2 的暴力菊花都卡不掉,但是没有 A,好多人造数据没卡掉,我不信邪啊,造了个万花筒(玫瑰花)数据给他卡了,jijidawang 甚至邀请 miaomiao 把这个加到数据里(鞭尸了属于是)。

hack 数据生成器
#include<bits/stdc++.h>
using namespace std;
signed main()
{
    freopen("/dev/urandom","r",stdin);
    freopen("rand.out","w",stdout);
    srand(getchar()*getchar()*getchar()*time(0));
    puts("1");
    int n=10000,m=40000;
    printf("%d %d\n",n,m);
    for(int i=2;i<=n;i++)
    {
        int w=rand()%1000+1;
        printf("%d %d %d\n",1,i,w);
    }
    int t=0;
    for(int i=2;i<=n;i++)
        for(int j=i+1;j<=n;j++)
        {
            t++;
            int w=rand()%1000+1;
            printf("%d %d %d\n",i,j,w);
            if(t+n-1>=m) return 0;
        }
}

T1 数字三角形

签到题,从后往前处理,能往下铺就往下铺,否则就往左铺,当然反过来也一样,可以证明其一定恰好铺完所有位置且联通。

点击查看代码
#include<bits/stdc++.h>
#define ll long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=510;
template<typename Tp> inline void read(Tp&x)
{
	x=0;register bool z=true;
	register char c=getchar();
	for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
	for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
	x=(z?x:~x+1);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x)
{if(x>9)wt(x/10);putchar((x%10)+'0');}
template<typename Tp> inline void write(Tp x)
{if(x<0)putchar('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar(' ');write(y...);}
int n,a[N],ans[N][N];
void dfs(int pos,int sum,int x,int y)
{
	if(sum==0) return ;
	ans[x][y]=pos;
	if(!ans[x+1][y]&&x+1<=n) dfs(pos,sum-1,x+1,y);
	else dfs(pos,sum-1,x,y-1);
}
signed main()
{
	read(n);
	for(int i=1;i<=n;i++) read(a[i]);
	for(int i=n;i>=1;i--) dfs(a[i],a[i],i,i);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
			write(ans[i][j]),putchar(' ');
		puts("");
	}
}

T2 那一天她离我而去

  • 部分分 \(23pts\):纯爆搜,能且只能过 \(n=m\) 的基环树数据。

  • 部分分 \(76pts\)\(n=m\) 的按照上面处理,其他的枚举所有与 \(1\) 连边的点 \(x\) 并断开这条边跑最短路,答案为 \(dis_x+w(1,x)\),若用 dijkstra 复杂度为 \(O(nm\log n)\)。(好像不特判 \(n=m\) 也是这个分?数据太水。)

  • 部分分 \(76pts\):有人是这么干的,爆搜加剪枝。

  • 正解:

    会这个正解前需要先会一个同样甚至更暴力的算法,从与 \(1\) 相连的一个点 \(x\) 出发跑最短路,则答案为 \(\min\limits_{y\in son_1}\{dis_y+w(1,y)\}\),枚举这个 \(x\),跑完最短路后再枚举 \(y\),复杂度为 \(O(nm\log n+n^2)\)

    考虑这么做多个 \(n^2\) 十分唐氏,于是建了个超级汇点,将与 \(1\) 相连的点抽出一部分与 \(1\) 断开去连接 \(n+1\), 边权和原来一致,那么 \(1\) 作为超级源点跑最短路,最后答案就是 \(dis_{n+1}\)

    想到这里正解就差不多了,需要满足任意两个节点存在一次二者不同时连接 \(1\)\(n+1\),想到二进制拆分,将其赋予下标的值,那么任意两点用二进制表示定至少一位不同,那么对于第 \(i\) 位上为 \(1\) 的连接 \(1\),剩下的连接 \(n+1\),再跑最短路,做到了不重不漏。

    复杂度 \(O(m\log^2 n)\)

    CuFeO4 有建最短路 dag 的单 \(\log\) 做法,但我不会 \(dag\)

    以上复杂度均针对 dijkstra 且菊花图,至于最短路方面,由于没有卡 spfa,所以 spfa 实际更快。

    点击查看代码
    #include<bits/stdc++.h>
    #define ll long long 
    #define endl '\n'
    #define sort stable_sort
    using namespace std;
    const int N=2e4+10,M=8e4+10;
    template<typename Tp> inline void read(Tp&x)
    {
        x=0;register bool z=true;
        register char c=getchar();
        for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
        for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
        x=(z?x:~x+1);
    }
    template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
    template<typename Tp> inline void wt(Tp x)
    {if(x>9)wt(x/10);putchar((x%10)+'0');}
    template<typename Tp> inline void write(Tp x)
    {if(x<0)putchar('-'),x=~x+1;wt(x);}
    template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar(' ');write(y...);}
    int T,n,m,cnt,ans,dis[N];
    bool vis[N];
    pair<int,int>pos[N];
    int tot,head[N],to[M],nxt[M],w[M];
    int last,_head[N],_to[M],_nxt[M],_w[M];
    void add(int x,int y,int z)
    {
        nxt[++tot]=head[x];
        to[tot]=y;
        w[tot]=z;
        head[x]=tot;
    }
    void spfa(int s)
    {
        queue<int>q;
        memset(dis,0x3f,sizeof(dis));
        memset(vis,0,sizeof(vis));
        dis[s]=0,vis[s]=1,q.push(s);
        while(!q.empty())
        {
            int x=q.front();
            q.pop();
            vis[x]=0;
            for(int i=head[x];i;i=nxt[i])
            {
                int y=to[i],z=w[i];
                if(dis[y]>dis[x]+z)
                {
                    dis[y]=dis[x]+z;
                    if(!vis[y]) 
                    {
                        vis[y]=1;
                        q.push(y);
                    }
                }
            }
        }
    }
    signed main()
    {
        read(T);
        while(T--)
        {
            memset(head,0,sizeof(head));
            memset(nxt,0,sizeof(nxt));
            memset(to,0,sizeof(to));
            memset(w,0,sizeof(w));
            cnt=tot=0,ans=0x3f3f3f3f;
            read(n,m);
            for(int i=1,x,y,z;i<=m;i++)
            {
                read(x,y,z);
                if(x>y) swap(x,y);
                if(x==1) pos[++cnt]=make_pair(y,z);
                else add(x,y,z),add(y,x,z);
            }
            memcpy(_head,head,sizeof(head));
            memcpy(_nxt,nxt,sizeof(nxt));
            memcpy(_to,to,sizeof(to));
            memcpy(_w,w,sizeof(w));
            last=tot;
            for(int i=0;(1<<i)<=cnt;i++)
            {
                memcpy(head,_head,sizeof(head));
                memcpy(nxt,_nxt,sizeof(nxt));
                memcpy(to,_to,sizeof(to));
                memcpy(w,_w,sizeof(w));
                tot=last;
                for(int j=1;j<=cnt;j++)
                {
                    if(j&(1<<i)) add(1,pos[j].first,pos[j].second);
                    else add(pos[j].first,n+1,pos[j].second);
                }
                spfa(1);
                ans=min(ans,dis[n+1]);
            }
            write(ans>=0x3f3f3f3f?-1:ans),puts("");
        }
    }
    

T3 哪一天她能重回我身边

貌似可做,但又不太会,先隔着。

T4 单调区间

  • 原题:Decinc Dividing

  • 其中的 \(check\) 部分还能单独弄出一个原题:Two Merged Sequences

  • 部分分 \(20pts\):纯爆搜。

  • 部分分 \(60pts\):加个剪枝。

  • 部分分 \(100pts\):考虑存在单调性,双指针跑,赛时 Pursuing_OIer 是这么写的,直接过了,(CF 过不去)。

  • 正解:

    这是一个分治玄学复杂度做法,实际复杂度和官方题解是一样的,都是带常数的 \(O(n)\),具体证明见官方题解:CF1693D Decinc Dividing

    在写正解之前我们需要一个复杂度更优秀的 \(check\),前面的 \(check\) 都是爆搜加剪枝的,复杂度玄学可卡。

    这个 \(check\) 贺的官方题解了,是个 DP:

    首先给出一个十分别扭的定义:

    \(f_{i,0}\) 表示第 \(i\) 个元素在 递增 序列中时,递减 序列最后一个元素的 最大值

    \(f_{i,1}\) 表示第 \(i\) 个元素在 递减 序列中时,递增 序列最后一个元素的 最小值

    初始值有 \(f_{i,0}=0,f_{i,1}=n+1,f_{l,0}=n+1,f_{l,1}=0\)

    枚举第 \(i-1\) 个元素在递增序列还是递减序列。然后尝试更新 \(f_{i,0||1}\),是要 \(f_{r,0}\ne 0||f_{r,1}\ne n+1\) 就说明存在合法方案。

    还可以顺便处理出以 \(i\) 为左端点,其合法的最远右端点,根据其单调性,下面有用。

    check
    void check(int x)
    {
        if(mx[x]) return ;
        f[x][0]=n+1,f[x][1]=0;
        for(int i=x+1;i<=n;i++)
        {
            f[i][0]=0,f[i][1]=n+1;
            if(f[i-1][0]!=0)//a[i-1] 能在递增末尾
            {
                if(a[i]>a[i-1]) f[i][0]=max(f[i][0],f[i-1][0]);//a[i] 接在递增末尾
                if(a[i]<f[i-1][0]) f[i][1]=min(f[i][1],a[i-1]);//a[i] 接在递减末尾
            }
            if(f[i-1][1]!=n+1)//a[i-1] 能在递减末尾
            {
                if(a[i]<a[i-1]) f[i][1]=min(f[i][1],f[i-1][1]);//a[i] 接在递减末尾
                if(a[i]>f[i-1][1]) f[i][0]=max(f[i][0],a[i-1]);//a[i] 接在递增末尾
            }
            if(f[i][0]==0&&f[i][1]==n+1) {mx[x]=i-1; return ;}
        }
        mx[x]=n;//以 x 为左端点能到达最远的右端点
    }
    

    然后根据决策单调性,显然有对于任何 \(i\)\(mx_{i}\ge mx_{i-1}\),那么对于一个区间 \([l,r]\),若 \(mx_{l}=mx_{r}\),则这一段区间的所有 \(mx_i\) 均为 \(mx_l\),根据此考虑分治,若不相等则递归 \([l,mid],[mid,r]\),这么做比递归 \([l,mid],[mid+1,r]\) 能少一个 \(check\),推荐这么写。

    点击查看代码
    #include<bits/stdc++.h>
    #define ll long long 
    #define endl '\n'
    #define sort stable_sort
    using namespace std;
    const int N=2e5+10;
    template<typename Tp> inline void read(Tp&x)
    {
        x=0;register bool z=true;
        register char c=getchar();
        for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
        for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
        x=(z?x:~x+1);
    }
    template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
    template<typename Tp> inline void wt(Tp x)
    {if(x>9)wt(x/10);putchar((x%10)+'0');}
    template<typename Tp> inline void write(Tp x)
    {if(x<0)putchar('-'),x=~x+1;wt(x);}
    template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar(' ');write(y...);}
    int n,a[N],f[N][2],mx[N];
    ll ans;
    void check(int x)
    {
        if(mx[x]) return ;
        f[x][0]=n+1,f[x][1]=0;
        for(int i=x+1;i<=n;i++)
        {
            f[i][0]=0,f[i][1]=n+1;
            if(f[i-1][0]!=0)
            {
                if(a[i]>a[i-1]) f[i][0]=max(f[i][0],f[i-1][0]);
                if(a[i]<f[i-1][0]) f[i][1]=min(f[i][1],a[i-1]);
            }
            if(f[i-1][1]!=n+1)
            {
                if(a[i]<a[i-1]) f[i][1]=min(f[i][1],f[i-1][1]);
                if(a[i]>f[i-1][1]) f[i][0]=max(f[i][0],a[i-1]);
            }
            if(f[i][0]==0&&f[i][1]==n+1) {mx[x]=i-1; return ;}
        }
        mx[x]=n;
    }
    void solve(int l,int r)
    {
        if(l>=r-1) return ;
        if(mx[l]==mx[r]) for(int i=l;i<=r;i++) mx[i]=mx[l];
        else 
        {
            int mid=(l+r)>>1;
            check(mid);
            solve(l,mid),solve(mid,r);
        }
    }
    signed main()
    {
        read(n);
        for(int i=1;i<=n;i++) read(a[i]);
        check(1),check(n);
        solve(1,n);
        for(int i=1;i<=n;i++) ans+=mx[i]-i+1;
        write(ans);
    }
    

总结

感觉……没啥可总结的,最近心情不是特别好但没有特别差,主要是因为今天打的还凑合,不然心态又要炸了。

除了 T4 忘了剪枝别的都挺好的,那个 T2、T3 正解看起来就不像我赛时能想出来的。

附录

  • 今天 T2、T3 选自往届模拟赛【失恋三部曲】(还有一个没放进来),然后昨天是七夕节,今天放这个题目有理由怀疑是故意的(反正不关我事)。

  • 关于 CF 把所有爬都 ban 了,现在交 CF 的题只能上 CF 交了(vjudge 都不行)。

  • 本来所今天没模拟赛的,到了晚上快放学了才把时间改了,以后不要这么做了谢谢(我没什么损失,但貌似好多人都准备今天改题呢),但明天确确实实没有模拟赛,课件都放了。

  • 每天中午起床来机房都和渡劫一样,又困又热离得还远还要抓紧时间不迟到。

  • 今天属于剪枝专场,优秀的剪枝可以给一个纯爆搜的同志加高达 \(84pts\)(我不提是谁)。

posted @ 2024-08-12 21:25  卡布叻_周深  阅读(69)  评论(0)    收藏  举报