冲刺国赛模拟 26

突然发现西安这段日子八场我打了三场还都是掉分。菜死了。

把精神状态固定在一个较低的水平会不会显得很弱智。不过我心理年龄大概在小六水平就不说什么了。

为啥我 tm 犯蠢没换新刀片。这玩意是不是和嗑药本质相同。

博鲁夫卡

曾经和 joke3579 一起做过这套的 E。对就是没题解的那场 ucup。当时他给我报了一遍所有题意,到这个题的时候说是个大板子。

容易发现 \(10^9\) 个点没啥用处,只要把不是边的端点的连续点缩成一个就行了。然后剩下 \(400000\) 个点跑某个姓 B 的最小生成树算法。每轮可能贡献的只有相邻块的边和给的 \(m\) 条边,找相邻块的边可以暴力扫,因此复杂度 \(O(m\log n)\)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
#define int long long
using namespace std;
int n,m,ans,fa[400010],lsh[400010],pos[400010],pre[400010],suf[400010];
pair<int,int>mn[400010];
vector<pair<int,int> >g[400010];
struct gra{
    int u,v,w;
}edge[400010];
int find(int x){
    return x==fa[x]?fa[x]:fa[x]=find(fa[x]);
}
void merge(int x,int y){
    fa[find(y)]=find(x);
}
int cnt;
struct node{
    int l,r,id;
}a[400010];
bool vis[400010];
signed main(){
    int tim;scanf("%lld",&tim);
    while(tim--){
        scanf("%lld%lld",&n,&m);cnt=ans=lsh[0]=0;
        for(int i=1;i<=m;i++){
            scanf("%lld%lld%lld",&edge[i].u,&edge[i].v,&edge[i].w);
            lsh[++lsh[0]]=edge[i].u;lsh[++lsh[0]]=edge[i].v;
        }
        sort(lsh+1,lsh+lsh[0]+1);
        lsh[0]=unique(lsh+1,lsh+lsh[0]+1)-lsh-1;
        for(int i=1;i<=m;i++){
            edge[i].u=lower_bound(lsh+1,lsh+lsh[0]+1,edge[i].u)-lsh;
            edge[i].v=lower_bound(lsh+1,lsh+lsh[0]+1,edge[i].v)-lsh;
            g[edge[i].u].push_back(make_pair(edge[i].v,edge[i].w));
            g[edge[i].v].push_back(make_pair(edge[i].u,edge[i].w));
        }
        if(lsh[1]!=1)a[++cnt]={1,lsh[1]-1,0},ans+=lsh[1]-2;
        for(int i=1;i<=lsh[0];i++){
            a[++cnt]={lsh[i],lsh[i],i};pos[i]=cnt;
            if(i!=lsh[0]&&lsh[i]+1<lsh[i+1])a[++cnt]={lsh[i]+1,lsh[i+1]-1,0},ans+=lsh[i+1]-lsh[i]-2;
        }
        if(lsh[lsh[0]]!=n)a[++cnt]={lsh[lsh[0]]+1,n,0},ans+=n-lsh[lsh[0]]-1;
        for(int i=0;i<=cnt+1;i++)fa[i]=i;
        int ret=cnt;
        while(ret>1){
            for(int i=1;i<=cnt;i++){
                if(find(i-1)!=find(i))pre[i]=i-1;
                else pre[i]=pre[i-1];
            }
            for(int i=cnt;i>=1;i--){
                if(find(i)!=find(i+1))suf[i]=i+1;
                else suf[i]=suf[i+1];
            }
            for(int i=1;i<=cnt;i++)mn[i]=make_pair(0x3f3f3f3f,0);
            for(int i=1;i<=cnt;i++){
                for(pair<int,int>p:g[a[i].id])vis[p.first]=true;
                for(int j=i;j>=1;j--){
                    if(find(i)==find(j))j=pre[j];
                    if(!j)break;
                    if(!vis[a[j].id]){
                        mn[find(i)]=min(mn[find(i)],make_pair(a[i].l-a[j].r,j));
                        break;
                    }
                }
                for(int j=i;j<=cnt;j++){
                    if(find(i)==find(j))j=suf[j];
                    if(j>cnt)break;
                    if(!vis[a[j].id]){
                        mn[find(i)]=min(mn[find(i)],make_pair(a[j].l-a[i].r,j));
                        break;
                    }
                }
                for(pair<int,int>p:g[a[i].id]){
                    vis[p.first]=false;
                    if(find(pos[p.first])!=find(i))mn[find(i)]=min(mn[find(i)],make_pair(p.second,pos[p.first]));
                }
            }
            for(int i=1;i<=cnt;i++){
                if(!mn[i].second)continue;
                if(find(i)!=find(mn[i].second)){
                    ans+=mn[i].first;
                    merge(i,mn[i].second);
                    ret--;
                }
            }
		}
        printf("%lld\n",ans);
        for(int i=1;i<=lsh[0];i++)g[i].clear();
    }
    return 0;
}

路径压缩

赛时找到了原题,但是不会。

首先判无解:原先在同一连通块最后不在了不合法,最后并查集如果出环了也不合法。\(g\) 中并查集森林的根一定在 \(f\) 中是根,否则不合法。

容易发现最后如果 \(x\)\(y\) 的祖先,那么自从合并到一个连通块开始 \(x\) 就一直是 \(y\) 的祖先。于是 \(f\) 的并查集森林中所有的根的祖先关系构成一个序。出环了无解,否则可以拓扑排序得到一组合法的解。

构造解的时候首先倒着考虑拓扑序,使得每个点往后合并到自己后边的点,然后在每次合并的时候把所有的 \(f_i\neq g_i\) 的点 find 一次即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int n,f[1010],g[1010],ind[1010];
bool vis[1010];
int find(int x){
    return x==f[x]?f[x]:f[x]=find(f[x]);
}
struct Dsu{
    int fa[1010];
    int find(int x){
        return x==fa[x]?fa[x]:fa[x]=find(fa[x]);
    }
}a,b;
struct node{
    int v,next;
}edge[1010];
int t,head[1010];
void add(int u,int v){
    edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
struct ques{
    int od,x,y;
};
vector<ques>ans;
int pre[1010],ed[1010],to[1010],dfn[1010];
queue<int>q;
void solve(){
    scanf("%d",&n);ans.clear();t=0;dfn[0]=0;
    for(int i=1;i<=n;i++)head[i]=vis[i]=ind[i]=to[i]=ed[i]=0,pre[i]=i;
    for(int i=1;i<=n;i++)scanf("%d",&f[i]);
    for(int i=1;i<=n;i++)scanf("%d",&g[i]);
    for(int i=1;i<=n;i++)a.fa[i]=f[i],b.fa[i]=g[i];
    for(int i=1;i<=n;i++){
        if(f[i]==i)vis[i]=true;
    }
    for(int i=1;i<=n;i++){
        if(f[i]!=g[i]&&!vis[f[i]]){
            find(i);ans.push_back({1,i,0});
        }
    }
    for(int i=1;i<=n;i++){
        if(f[i]!=g[i]){
            if(!vis[g[i]]){
                puts("NO");return;
            }
            if(f[g[i]]==g[g[i]])ed[f[i]]=g[i];
            else add(f[i],g[i]),ind[g[i]]++;
        }
    }
    for(int i=1;i<=n;i++){
        if(vis[i]&&f[i]!=g[i]&&!ind[i])q.push(i);
    }
    while(!q.empty()){
        int x=q.front();q.pop();
        dfn[++dfn[0]]=x;
        for(int i=head[x];i;i=edge[i].next){
            ind[edge[i].v]--;
            if(!ind[edge[i].v])q.push(edge[i].v);
        }
    }
    for(int i=1;i<=n;i++)if(ind[i]){
        puts("NO");return;
    }
    for(int z=dfn[0];z>=1;z--){
        int x=dfn[z];
        for(int i=head[x];i;i=edge[i].next)ed[x]=ed[edge[i].v];
        to[x]=pre[ed[x]];pre[ed[x]]=x;
    }
    for(int i=1;i<=dfn[0];i++){
        int x=dfn[i];f[x]=to[x];
        ans.push_back({2,x,to[x]});
        for(int j=1;j<=n;j++){
            if(f[j]!=g[j]&&f[j]==x){
                find(j);
                ans.push_back({1,j,0});
            }
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            if(a.find(i)==a.find(j)&&b.find(i)!=b.find(j)){
                puts("NO");return;
            }
        }
    }
    puts("YES");
    printf("%d\n",(int)ans.size());
    for(ques x:ans){
        if(x.od==1)printf("%d %d\n",x.od,x.x);
        else printf("%d %d %d\n",x.od,x.x,x.y);
    }
}
int main(){
    int tim;scanf("%d",&tim);
    while(tim--)solve();
    return 0;
}

鱼了个鱼

怎么又这种东西。超级结论题。

没找到原题,大概不是 ucup 的,因为没找到。但是这个结论很熟悉,好像在哪里见过这题。是不是牛子老师在群里发过题解。

根据抽屉原理 \(S<N\) 一定有解,然后每个花色最大的是消不掉的,因此 \(S>N\) 一定无解。

然后有个结论是说如果能删则一定删。口胡的证明:假如说要删空这一堆,那么显然要删。如果要删空别的堆再把这个放上去,那么同样删比不删然后垫在底下要好。如果不把这个放上去那不影响。

那么我们现在是这样的过程:能删就删,如果有空还要决策一个把哪堆放到空的里边。那我们先删,一直到不能删。

这时候有个玄学的判定方法:建立源点 \(S\) 和汇点 \(T\),将每种最大值在栈底的元素看做一个点,如果一种颜色只有最大值那么和源点连边,如果一个栈里有若干种颜色的最大值那么它们向汇点连边,然后对于颜色 \(x\) 的最大值和颜色 \(y\) 的次大值在同一个栈里,连 \(x\to y\) 边。如果源点能到汇点则合法。证明不会。

代码没写。感觉浪费时间。

posted @ 2023-06-28 20:25  gtm1514  阅读(24)  评论(0编辑  收藏  举报