随机化乱搞♂

随机化大概分为两种,一种是有依据或者有证明的随机,一种是乱搞♂优化暴力。

BZOJ3569 DZY Loves Chinese II

给一张无向图,每次删掉一些边(非永久操作),问图是否连通。

N≤100000 M≤500000 Q≤50000 1≤K≤15

数据保证没有重边与自环

严肃的随机化

取一个生成树,对每条非树边取一个随机权值,对每条树边设为“覆盖它的所有非树边”的权值的xor,这个可以用类似树上差分的方式实现。

对于每次询问,只要某个子集的所有边xor值是0,那么就不连通,否则连通。

判断用线性基就好了。时间复杂度\(O(n+\Sigma k \times 64)\)

这个随机化是正确的做法。

#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read(){
    T x=0,w=1;char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*w;
}
template<class T> T read(T&x){
    return x=read<T>();
}
using namespace std;
typedef long long LL;

il LL van(){
    return rand()|rand()<<15|(LL)rand()<<30|(LL)rand()<<45|(LL)rand()<<60;
}

co int N=100000+10;
int n,m;
struct edge{int y,next;}pool[10*N];
int head[N],tot;

il void add_edge(int x,int y){
    pool[++tot]=(edge){y,head[x]},head[x]=tot;
}
int dep[N],adj[N];
LL dif[N],val[5*N];
void dfs(int x,int fa){
    for(int i=head[x];i;i=pool[i].next){
        int y=pool[i].y;
        if(y==fa) continue;
        if(!dep[y]){
            dep[y]=dep[x]+1,adj[y]=(i+1)>>1;
            dfs(y,x),
            dif[x]^=dif[y];
        }
        else if(dep[y]<dep[x]){
            int id=(i+1)>>1;
            val[id]=van();
            dif[x]^=val[id],dif[y]^=val[id];
        }
    }
    val[adj[x]]=dif[x];
}

LL bas[64];
bool insert(LL v){
    for(int i=63;v&&i>=0;--i)if(v>>i&1){
        if(!bas[i]) return bas[i]=v,1;
        else v^=bas[i];
    }
    return 0;
}
int main(){
    srand(20030506);
    read(n),read(m);
    for(int i=1;i<=m;++i){
        int x=read<int>(),y=read<int>();
        add_edge(x,y),add_edge(y,x);
    }
    dfs(1,0);
    int ans=0;
    for(int q=read<int>();q--;){
        int k=read<int>();
        bool valid=1;
        memset(bas,0,sizeof bas);
        for(int i=1;i<=k;++i)
            if(!insert(val[read<int>()^ans])) valid=0;
        ans+=valid,puts(valid?"Connected":"Disconnected");
    }
    return 0;
}

[HNOI/AHOI2018]游戏

有一排房间,有些房间之间有门。\(x_i\)\(x_i+1\) 的门的钥匙在 \(y_i\),每次查询 \(s \rightarrow t\) 是否可达。

\(n,q \le 10^6\)

题解

罕见的用到栈的好题。

我们只需预处理出每个点\(i\)能到的范围\([l_i,r_i]\),就可以回答询问了。

先确定一个大致的范围\([L_i,R_i]\),满足\(L_i\)\(i\)左侧最靠右的\(key_{j}<j\)的位置的后面那个点,\(R_i\)\(i\)右侧最靠左的\(key_j>j\)的位置的点。这个可以\(O(n)\)预处理。

显然\([l_i,r_i] \subseteq [L_i,R_i]\)。先让\(l_i=L_i,r_i=R_i\)然后我们可以用线段树来找\([i,r_i-1]\)第一个钥匙在\(l_i\)左侧的点来更新\(r_i\),找\([l_i,i-1]\)最后一个钥匙在\(r_i\)右侧的点来更新\(l_i\)。但是这样会反复做下去,时间复杂度没有保证。

考虑如果\(i<j\),且\(i\)可达\(j\),那么凡是可达\(i\)的都可达\(j\),所以我们有些计算是重复了的。所以可以用栈来维护后缀可达性的分割,这样进行迭代更新的话复杂度是均摊\(O(n \log n)\)的。

#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read(){
    T x=0,w=1;char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*w;
}
template<class T> il T read(T&x){
    return x=read<T>();
}
using namespace std;
typedef long long LL;

co int N=1000000+10;
int n,m,q;
int key[N],L[N],R[N];
int st[N],top,l[N],r[N];

int s[N<<2];
#define lc (x<<1)
#define rc (x<<1|1)
void build(int x,int l,int r){
    if(l==r){
        s[x]=key[l];
        return;
    }
    int mid=(l+r)>>1;
    build(lc,l,mid),build(rc,mid+1,r);
    s[x]=max(s[lc],s[rc]);
}
int get(int x,int l,int r,int p){
    if(s[x]<=p) return 0;
    if(l==r) return l;
    int mid=(l+r)>>1;
    return s[rc]>p?get(rc,mid+1,r,p):get(lc,l,mid,p);
}
int query(int x,int l,int r,int ql,int qr,int p){
    if(ql<=l&&r<=qr) return get(x,l,r,p);
    int mid=(l+r)>>1,pos=0;
    if(qr>mid) pos=query(rc,mid+1,r,ql,qr,p);
    if(pos) return pos;
    if(ql<=mid) pos=query(lc,l,mid,ql,qr,p);
    return pos;
}
il int find_pos(int l,int r,int p){
    if(l>r) return l;
    int pos=query(1,1,n,l,r,p);
    return pos?pos+1:l;
}

int main(){
    read(n),read(m),read(q);
    while(m--) key[read<int>()]=read<int>(); // priority
    build(1,1,n);
    key[n]=n+1,key[0]=-1;
    for(int i=1;i<=n;++i) L[i]=key[i-1]&&key[i-1]<i?i:L[i-1];
    for(int i=n;i>=1;--i) R[i]=key[i+1]&&key[i]>i?i:R[i+1];
    for(int i=n;i>=1;--i){
        r[i]=i,l[i]=find_pos(L[i],i-1,r[i]);
        st[++top]=i;
        while(top&&(l[i]<=key[st[top]]&&key[st[top]]<=r[i]||!key[st[top]]))
            r[i]=st[--top],l[i]=find_pos(L[i],l[i]-1,r[i]);
    }
    while(q--){
        int x=read<int>(),y=read<int>();
        puts(l[x]<=y&&y<=r[x]?"YES":"NO");
    }
    return 0;
}

当然这个做法以及更加专业的拓扑排序都离题了。

欢乐的随机化

先考虑最朴素的暴力,枚举每个点 x ,从 x 往两边跳,如果去下个点的门没有锁,或者钥匙的位置在 [Lx,Rx] 内,就继续拓展.

有一个比较简单的优化,拓展到了一个点 y 后,就用 y 的 L,R 值更新 x 的.这样做能获得 20 分的好成绩.

我们对枚举 x 的顺序随机化,执行上述的算法.实践可以获得 100 分.

#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read(){
    T x=0,w=1;char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*w;
}
template<class T> il T read(T&x){
    return x=read<T>();
}
using namespace std;
typedef long long LL;

co int N=1000000+10;
int n,m,q;
int key[N],L[N],R[N];
int ord[N];

void solve(int x){
    int&l=L[x]=x,&r=R[x]=x;
    while(1){
        bool valid=0;
        if(l>1&&(!key[l-1]||l<=key[l-1]&&key[l-1]<=r))
            valid=1,--l,r=max(r,R[l]),l=min(l,L[l]);
        if(r<n&&(!key[r]||l<=key[r]&&key[r]<=r))
            valid=1,++r,l=min(l,L[r]),r=max(r,R[r]);
        if(!valid) break;
    }
}
int main(){
    read(n),read(m),read(q);
    while(m--) key[read<int>()]=read<int>();
    for(int i=1;i<=n;++i) ord[i]=i,L[i]=n+1,R[i]=0;
    srand(19260817);
    random_shuffle(ord+1,ord+n+1);
    for(int i=1;i<=n;++i) solve(ord[i]);
    while(q--){
        int s=read<int>(),t=read<int>();
        puts(L[s]<=t&&t<=R[s]?"YES":"NO");
    }
    return 0;
}

BZOJ5397 circular

众所周知, 小 c 非常 circular, 因此他很喜欢环。

他有一个长度为 M 的环, 环上有 M 个等距离的点, 按顺时针顺序依次标号为 0, 1, . . . , M − 1。

环上有 N 个线段 (ai, bi)(1 ≤ i ≤ N), 需要注意的是 (ai, bi) 所指的线段是从点 ai 顺时针延伸到 bi 的线段。

小 c 希望知道最多能选多少个不相交的线段, 注意线段的端点是允许重合的。

对于 100% 的数据: N ≤ 100000, M ≤ 108, 0 ≤ ai, bi < M (ai ̸= bi)

题解

一道倍增好题。用到倍增的都是好题。

根据套路,拆环为链复制一倍。先去掉那些包含了其他线段的线段,因为它们肯定没有用。

然后可以对每条线段找到它之后第一个与它相离的线段。注意这个关系是可以按数量成段转移的,所以可以倍增。

这样我们可以对每个线段二分答案,用 倍增结果是否存在且合法 来判断答案的合法性。

时间复杂度\(O(n \log^2 n)\)

#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read(){
    T x=0,w=1;char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*w;
}
template<class T> il T read(T&x){
    return x=read<T>();
}
using namespace std;
typedef long long LL;

co int N=100000+10;
int m,n,tot;
struct node{int l,r;}a[N<<1],b[N<<1];
il bool operator<(co node&a,co node&b){
    return a.l<b.l||(a.l==b.l&&a.r>b.r);
}
int st[N<<1],top,vis[N<<1];
int nn,fa[N<<1][19];

bool check(int len,int i){
    int l=b[i].l;
    for(int j=0;1<<j<=len;++j)
        if(len>>j&1) i=fa[i][j];
    if(!i) return 0;
    return l+m>=b[i].r;
}

int main(){
    read(m),read(n);
    for(int i=1;i<=n;++i){
        int l=read<int>(),r=read<int>();
        if(l>r) r+=m;
        a[++tot]=(node){l,r},a[++tot]=(node){l+m,r+m};
    }
    sort(a+1,a+tot+1);
    for(int i=1;i<=tot;++i){
        while(top&&a[st[top]].r>=a[i].r) vis[st[top--]]=1;
        st[++top]=i;
    }
    for(int i=1;i<=tot;++i)
        if(!vis[i]) b[++nn]=a[i];
    for(int i=1,j=1;i<=nn;++i){
        while(b[j].l<b[i].r&&j<=nn) ++j;
        if(j>nn) break;
        fa[i][0]=j;
    }
    for(int j=1;1<<j<=nn;++j)
        for(int i=1;i<=nn;++i) fa[i][j]=fa[fa[i][j-1]][j-1];
    int ans=0;
    for(int i=1;i<=nn;++i){
        int l=0,r=n-1;
        while(l<r){
            int mid=(l+r+1)>>1;
            if(check(mid,i)) l=mid;
            else r=mid-1;
        }
        ans=max(ans,r+1);
    }
    printf("%d\n",ans);
    return 0;
}

欢乐的随机化

倍长环,断环为链。

我们考虑枚举开头的线段,然后做一次贪心。这样子的复杂度根据实现的不同是O(n2 log⁡ n)或者O(n2)。

不妨假设我们不知道倍增能优化,我们考虑答案的构成,记答案为B。

  • 如果B<√n,那么我们只需要每次跳B次就可以出解
  • 如果B>√n,那么我们随机取n/B个线段作为端点,然后取最优值

这样子,我们就得到了一个看起来完全不对劲的O(n√n)的算法

但是人都是懒惰的,我们考虑直接用第二种方法。经过实践,实际上只要取2个线段作为端点就足够A掉本题了。复杂度O(n log ⁡n),虽然正确性完全无法保证呢。

#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read(){
    T x=0,w=1;char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*w;
}
template<class T> il T read(T&x){
    return x=read<T>();
}
using namespace std;
typedef long long LL;

co int N=200000+10;
int n,m,tot;
int L[N],R[N];

struct seg{int l,r;}A[N];
il bool operator<(co seg&a,co seg&b){
    return a.r>b.r;
}

priority_queue<seg> q;

il int solve(int o){
    int ans=0,nr=-1;
    for(int i=1;i<=tot;++i)
        if(A[i].l>=L[o]&&A[i].r<=L[o]+m) q.push(A[i]);
    while(q.size()){
        seg B=q.top();q.pop();
        if(B.l<nr) continue;
        nr=max(nr,B.r),++ans;
    }
    return ans;
}

int main(){
    read(m),read(n);
    for(int i=1;i<=n;++i){
        int l=read<int>(),r=read<int>();
        L[i]=l,R[i]=r;
        if(l>r) A[++tot]=(seg){l,r+m};
        else A[++tot]=(seg){l,r},A[++tot]=(seg){l+m,r+m};
    }
    int ans=0,k=2;
    for(int i=1;i<=n;i+=n/k) ans=max(ans,solve(i));
    printf("%d\n",ans);
    return 0;
}

BZOJ4966 总统选举

秋之国共有n个人,分别编号为1,2,…,n,一开始每个人都投了一票,范围1~n,表示支持对应编号的人当总统。

共有m次预选,每次选取编号[li,ri]内的选民展开小规模预选,在该区间内获得超过区间大小一半的票的人获胜,如果没有人获胜,则由小C钦定一位候选者获得此次预选的胜利(获胜者可以不在该区间内)。每次预选的结果需要公布出来,并且每次会有ki个人决定将票改投向该次预选的获胜者。

全部预选结束后,公布最后成为总统的候选人

1<=n,m<=500,000,Σki<=1,000,000,1<=li<=ri<=n,1<=si<=n。

题解

学习了一种新套路,我真是孤陋寡闻……看来有必要刷一下BZOJ 上的线段树题了。

这种“出现次数超过一半”模型,可以使用摩尔投票做法。

这个“超过一半”的性质就是

  1. 区间内剩余的数的个数总和没有它多,并且这种数在一个区间内至多有一个。
  2. 如果一个人在一个区间中占有超过一半的票数,那么把这个区间分成任意两个,他肯定在至少一个区间中占有超过一半的票数。

摩尔投票做法就是:线段树维护每个点的最有可能是答案的数以及它的权重。合并两个节点的时候,将权重互相抵消,保留较大的那一个。

得到答案后,再在对应权值的Treap中查询出现次数,检查是否真正是答案。

时间复杂度\(O(n \log n)\)。我在BZOJ上TLE了,在洛谷上过了。

#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read(){
    T x=0,w=1;char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*w;
}
template<class T> il T read(T&x){
    return x=read<T>();
}
using namespace std;
typedef long long LL;

co int N=500000+10;
int n,m,vot[N];

namespace Seg{
    struct node {int vot,num;};
    il node operator+(co node&a,co node&b){
        if(!a.num||!b.num) return (node){a.vot+b.vot,a.num+b.num};
        if(a.vot==b.vot) return (node){a.vot,a.num+b.num};
        if(a.num==b.num) return (node){0,0};
        return a.num>b.num?(node){a.vot,a.num-b.num}:(node){b.vot,b.num-a.num};
    }
    
    int l[N<<2],r[N<<2];
    node s[N<<2];
    #define lc (x<<1)
    #define rc (x<<1|1)
    void build(int x,int l,int r){
        Seg::l[x]=l,Seg::r[x]=r;
        if(l==r){
            s[x]=(node){vot[l],1};
            return;
        }
        int mid=(l+r)>>1;
        build(lc,l,mid),build(rc,mid+1,r);
        s[x]=s[lc]+s[rc];
    }
    node query(int x,int ql,int qr){
        if(ql<=l[x]&&r[x]<=qr) return s[x];
        int mid=(l[x]+r[x])>>1;
        if(qr<=mid) return query(lc,ql,qr);
        if(ql>mid) return query(rc,ql,qr);
        return query(lc,ql,qr)+query(rc,ql,qr);
    }
    void change(int x,int p){
        if(l[x]==r[x]){
            s[x]=(node){vot[p],1};
            return;
        }
        int mid=(l[x]+r[x])>>1;
        if(p<=mid) change(lc,p);
        else change(rc,p);
        s[x]=s[lc]+s[rc];
    }
}

namespace Tre{
    int st[N],top;
    int root[N],tot;
    int ch[N][2],siz[N],pri[N],val[N];
    
    il int new_node(int v){
        int x;
        if(top) x=st[top--];
        else x=++tot;
        ch[x][0]=ch[x][1]=0,siz[x]=1;
        pri[x]=rand()|rand()<<15,val[x]=v;
        return x;
    }
    il void push_up(int x){
        siz[x]=siz[ch[x][0]]+1+siz[ch[x][1]];
    }
    void split(int x,int v,int&l,int&r){
        if(!x){
            l=r=0;
            return;
        }
        if(val[x]<=v){
            l=x;
            split(ch[l][1],v,ch[l][1],r);
            push_up(l);
        }
        else{
            r=x;
            split(ch[r][0],v,l,ch[r][0]);
            push_up(r);
        }
    }
    int merge(int x,int y){
        if(!x||!y) return x+y;
        if(pri[x]>pri[y]){
            ch[x][1]=merge(ch[x][1],y);
            push_up(x);
            return x;
        }
        else{
            ch[y][0]=merge(x,ch[y][0]);
            push_up(y);
            return y;
        }
    }
    int query(int t,int u,int v){
        int l,m,r;
        split(root[t],u-1,l,m),split(m,v,m,r);
        int ans=siz[m];
        root[t]=merge(l,merge(m,r));
        return ans;
    }
    void insert(int t,int v){
        int l,r;
        split(root[t],v,l,r);
        root[t]=merge(l,merge(new_node(v),r));
    }
    void remove(int t,int v){
        int l,m,r;
        split(root[t],v,m,r),split(m,v-1,l,m);
        st[++top]=m,m=merge(ch[m][0],ch[m][1]);
        root[t]=merge(l,merge(m,r));
    }
}


int main(){
    srand(19260817);
    read(n),read(m);
    for(int i=1;i<=n;++i){
        read(vot[i]);
        Tre::insert(vot[i],i);
    }
    Seg::build(1,1,n);
    while(m--){
        int l=read<int>(),r=read<int>();
        int vot=Seg::query(1,l,r).vot;
        if(Tre::query(vot,l,r)<<1<=r-l+1) vot=-1;
        vot!=-1?read<int>():vot=read<int>();
        printf("%d\n",vot);
        for(int k=read<int>();k--;){
            int i=read<int>();
            Tre::remove(::vot[i],i);
            Tre::insert(::vot[i]=vot,i);
            Seg::change(1,i);
        }
    }
    int vot=Seg::s[1].vot;
    if(Tre::query(vot,1,n)<<1<=n) vot=-1;
    printf("%d\n",vot);
    return 0;
}

严肃的随机化

事实上没必要写线段树,只用随机化就行了,每次在区间里随机取k个数,然后用平衡树判断是否在区间里出现次数超过一半即可。

这样的话,如果出现次数大于一半是数存在,没有随机到出现大于一半数的概率 肯定小于 \(\frac{1}{2^k}\)

稳妥起见 k 取 30 比较好,但是本题极度卡常,所以说这样可能会被卡

其实k取小一点是可以的,取14,然后平衡树用pb_ds写。

#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read(){
    T x=0,w=1;char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*w;
}
template<class T> il T read(T&x){
    return x=read<T>();
}
using namespace std;
typedef long long LL;

#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;

co int N=500000+10;
tree<LL,null_type,less<LL>,rb_tree_tag,tree_order_statistics_node_update> s[N];
int n,m,a[N];

il int query(int x,int l,int r){
    return s[x].order_of_key(r+1)-s[x].order_of_key(l);
}
int solve(int l,int r){
    int n=r-l+1;
    for(int i=1;i<=14;++i){
        int x=a[rand()%n+l];
        if(query(x,l,r)<<1>n) return x;
    }
    return -1;
}
int main(){
    srand(19260817);
    read(n),read(m);
    for(int i=1;i<=n;++i){
        read(a[i]);
        s[a[i]].insert(i);
    }
    while(m--){
        int l=read<int>(),r=read<int>();
        int ans=solve(l,r);
        ans!=-1?read<int>():ans=read<int>();
        printf("%d\n",ans);
        for(int k=read<int>();k--;){
            int i=read<int>();
            s[a[i]].erase(i);
            s[a[i]=ans].insert(i);
        }
    }
    printf("%d\n",solve(1,n));
    return 0;
}

Codechef Selling Tickets

厨师在一次晚宴上准备了n道丰盛的菜肴,来自世界各地的m位顾客想要购买宴会的门票。每一位顾客都有两道特别喜爱的菜,而只要吃到了至少一道他喜爱的菜,这位顾客就会感到很高兴。当然,每道菜最多只能供应给一位顾客。厨师想要卖出尽可能多的门票,但同时要能够保证,无论哪些顾客购买门票,所有到来的顾客都能感到高兴。现在,厨师想要问你,他最多能够卖多少门票?

1≤T≤15, 2≤n≤200, 0≤m≤500

1≤Ai, Bi≤n且Ai≠Bi

Editorial EXPLANATION

Let us consider a multigraph with N vertices and M edges, where each vertex corresponds to a dish, and each edge corresponds to a patron, connecting the vertices corresponding to that patron’s preferred dishes. For Chef to be able to sell K tickets, it means that for any set of K edges, the number of vertices touched by these edges is not less than the number of edges. Our task, then, is to find the smallest set of edges such that the number of vertices touched is less than the number of edges. The answer will be one less than the number of edges in this set.

What might such a set of edges look like? Let E be the number of edges and V the number of vertices. Because this is a minimal set, we must have E = V+1, otherwise we could simply remove an edge and have a smaller set satisfying the constraint. Also, there cannot be any vertices with degree 1, since the edge it touches could be removed as well. Since the sum of the degrees of all vertices is equal to twice the number of edges, there are only 2 possibilities: either 2 vertices have degree 3 and the rest have degree 2, or 1 vertex has degree 4 and the rest have degree 2.

We thus have only 3 possible structures for this graph formed by these edges:

  • Two vertices with degree 3, with 3 distinct paths between them.
  • Two vertices with degree 3, with 1 path between them and each attached to a cycle.
  • Two cycles that meet at a single vertex.

Each of these types of graphs can be searched for independently. The answer will be one less than the size of the smallest graph found.

Type 1 graphs: We can find such graphs by doing something similar to a single-source-shortest-path algorithm from each vertex, only we find the 3 shortest paths to each vertex. It doesn’t matter if the paths aren’t distinct, so long as each individual path doesn’t visit the same node twice.

Type 2 graphs: We begin by finding the shortest cycle through each vertex. Then for each pair of vertices, we make sure they don’t belong to the same shortest cycle, and take the lengths of the cycles plus the length of the shortest path between the two vertices as our graph size. It again doesn’t matter if paths aren’t distinct, as that simply implies the existence of a smaller graph.

Type 3 graphs: Here we again use a modified single-source-shortest-path algorithm from each vertex to find the 2 shortest cycles through a given vertex. Once again it doesn’t matter if these cycles aren’t distinct, so long as individually they don’t visit the same vertex twice.

这种ACM图论题挺难,但是也很有趣。

这种BFS+bitset找环,大概是\(O(\frac {n^4}{32})\)的?代码有点毒瘤啊……虽然加了注释不难理解。但既然我被安排到了图论专场,我就一定要把它写出来。

#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read(){
    T x=0,w=1;char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*w;
}
template<class T> il T read(T&x){
    return x=read<T>();
}
using namespace std;
typedef long long LL;

co int INF=1e9;
int n,edges;
vector<int> edge[200],eNum[200];
int dist[200][200];

struct state{
    bitset<200> vis;
    int u,in_edge;
    
    il state(bitset<200> mask,int v,int pedge)
    :vis(mask),u(v),in_edge(pedge){
        vis[u]=1;
    }
    il state move(int v,int pedge){
        return state(vis,v,pedge);
    }
};

// two vectices connected by three paths
vector<int> costs[200];
int chep_cycl[200];
bitset<200> chep_cycl_mask[200];

int find_three_paths(int start){
    for(int i=0;i<n;++i) costs[i].clear();
    
    queue<state> q;
    q.push(state(bitset<200>(),start,-1));
    while(q.size()){
        state&S=q.front();q.pop();
        int u=S.u;
        for(unsigned i=0;i<edge[u].size();++i){
            int v=edge[u][i];
            if(costs[v].size()<3&&!S.vis[v]){
                state next=S.move(v,eNum[u][i]);
                costs[v].push_back(next.vis.count()-1);
                q.push(next);
            }
            if(v==start&&costs[v].size()<3&&S.in_edge!=eNum[u][i]){
                if(costs[v].empty()) chep_cycl_mask[v]=S.vis;
                costs[v].push_back(S.vis.count());
            }
        }
    }
    
    int ans=INF;
    for(int i=0;i<n;++i){
        dist[i][start]=dist[start][i]=i!=start?INF:0;
        if(!costs[i].empty()) dist[i][start]=dist[start][i]=costs[i][0];
        if(i!=start&&costs[i].size()==3)
            ans=min(ans,costs[i][0]+costs[i][1]+costs[i][2]);
    }
    
    // two cycles sharing a vertex
    if(costs[start].size()==3)
        // remove costs[start][1] since same cycle will be repeated in opposite direction as well
        ans=min(ans,costs[start][0]+costs[start][2]);
    chep_cycl[start]=INF;
    if(!costs[start].empty()) chep_cycl[start]=costs[start][0];
    return ans-1;
}
int find_three_paths(){
    int ans=INF;
    for(int i=0;i<n;++i)
        ans=min(ans,find_three_paths(i));
    // two cycles joined by a path
    for(int i=0;i<n;++i) if(chep_cycl[i]!=INF)
        for(int j=i+1;j<n;++j) if(chep_cycl[j]!=INF&&!chep_cycl_mask[i][j])
            ans=min(ans,dist[i][j]+chep_cycl[i]+chep_cycl[j]-1);
    return ans;
}

void real_main(){
    read(n),read(edges);
    for(int i=0;i<n;++i) edge[i].clear(),eNum[i].clear();
    for(int i=0;i<edges;++i){
        int u=read<int>()-1,v=read<int>()-1;
        edge[u].push_back(v),eNum[u].push_back(i);
        edge[v].push_back(u),eNum[v].push_back(i);
    }
    
    printf("%d\n",min(edges,find_three_paths()));
}
int main(){
    for(int t=read<int>();t--;) real_main();
    return 0;
}

欢乐颂

简单的想法就是每次random_shuffle一个序列,沿着这个序列从左到右一直卖,如果卖到第i+1个卖不出去了,就用i更新答案。如何判定能不能卖出去呢?二分图最大匹配即可。

但是这种随机化策略实在是naive,我们考虑优化,如果卖到i+1个卖不出去了,我们看一下第i+1个人在二分图最大匹配上一共试图匹配了多少个人,即交错环的大小。并用交错环的大小来更新答案,然后就可以过了。

这种做法其实也是在利用补集转化思想,求正面最大→反面最小。

然而貌似这种做法只能过BZOJ数据?Codechef上面它狂WA不止。

#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read(){
    T x=0,w=1;char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*w;
}
template<class T> il T read(T&x){
    return x=read<T>();
}
using namespace std;
typedef long long LL;

co int N=510;
int n,m,p[N];
vector<int> to[N];
int vis[N],from[N],now,sum;

bool dfs(int x){
    ++sum;
    for(int i=0;i<(int)to[x].size();++i){
        int y=to[x][i];
        if(vis[y]==now) continue;
        vis[y]=now;
        if(!from[y]||dfs(from[y])){
            from[y]=x;
            return 1;
        }
    }
    return 0;
}
void real_main(){
    read(n),read(m);
    for(int i=1;i<=m;++i) p[i]=i,to[i].clear();
    now=0;
    for(int i=1;i<=n;++i) vis[i]=0;
    for(int i=1;i<=m;++i){
        to[i].push_back(read<int>());
        to[i].push_back(read<int>());
    }
    int ans=m;
    for(int T=2000;T--;){
        random_shuffle(p+1,p+m+1);
        fill(from+1,from+n+1,0);
        for(int i=1;i<=m;++i){
            ++now,sum=0;
            if(!dfs(p[i])){
                ans=min(ans,sum-1);
                break;
            }
        }
    }
    printf("%d\n",ans);
}
int main(){
    srand(19260817);
    for(int T=read<int>();T--;) real_main();
    return 0;
}

BZOJ3632 外太空旅行

在人类的触角伸向银河系的边缘之际,普通人上太空旅行已经变得稀松平常了。某理科试验班有n个人,现在班主任要从中选出尽量多的人去参加一次太空旅行活动。

可是n名同学并不是和平相处的。有的人,比如小A和小B整天狼狈为奸,是好朋友;但还有的人,比如杜鲁门和赫鲁晓夫就水火不相容。这n名同学,由于是理科生,都非常的理性,所以“朋友的朋友就是朋友”和“敌人的朋友就是敌人”这两句话对这些同学无效。换句话说,有可能小A和小B是朋友,小B和小C是朋友,但是小A和小C两人势如水火。

任意两个人之间要不就是敌人,要不就是朋友。

因为在太空船上发生人员斗殴事件是很恶劣也很危险的,因此选出来参加旅行活动的同学必须互相之间都是朋友。你的任务就是确定最多可以选多少人参加旅行。

欢乐的随机化

这个就是求一个图的最大团就是指从一个图中选出来一堆点,每对点间都直接有边相连。最大团就是指包含的点最多的那个团。这题就是个“最大团问题”

但不幸的是,最大团问题是NPC的。

目前最快的正确解法是状压DP,复杂度为 O( n2 2n )。但明显过不了这个题。

但这题只需要随机生成一个数列,并从前往后选,就可以AC了。

#include<bits/stdc++.h>
#define co const
#define il inline
using namespace std;
typedef long long LL;

co int N=60;
int n,p[N];
int gaycnt[N],gay[N][N];
int s[N],top;

bool check(int x){
    if(gaycnt[x]<top) return 0;
    for(int i=1;i<=top;++i)
        if(!gay[s[i]][x]) return 0;
    return 1;
}
int main(){
    srand(19260817);
    scanf("%d",&n);
    for(int i=1;i<=n;++i) p[i]=i;
    for(int a,b;scanf("%d%d",&a,&b)!=EOF;){
        gay[a][b]=gay[b][a]=1;
        ++gaycnt[a],++gaycnt[b];
    }
    
    int ans=0;
    while(clock()<0.9*CLOCKS_PER_SEC){
        top=0;
        random_shuffle(p+1,p+n+1);
        int sum=0;
        for(int i=1;i<=n;++i)
            if(check(p[i])) s[++top]=p[i],++sum;
        ans=max(ans,sum);
        if(ans==n) break;
    }
    printf("%d\n",ans);
    return 0;
}

BZOJ1278 向量vector

一个二维向量 (x,y) 的权定义为 x2+y2 。已知一个由 n 个二维向量组成的集合,求该集合的一个子集,使该子集中的向量和的权尽可能大。

题解

有一个结论是,构成答案的向量一定在某一个过原点的直线一侧

所以把直线排一遍序然后维护它们在同一侧就好了。可以利用双指针法。

时间复杂度O(n log n)​。

#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read(){
    T x=0,w=1;char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*w;
}
template<class T> il T read(T&x){
    return x=read<T>();
}
using namespace std;
typedef long long LL;

struct Vector{LL x,y;};
il bool operator<(co Vector&a,co Vector&b){
    return atan2(a.y,a.x)<atan2(b.y,b.x);
}
il Vector operator+(co Vector&a,co Vector&b){
    return (Vector){a.x+b.x,a.y+b.y};
}
il Vector operator-(co Vector&a,co Vector&b){
    return (Vector){a.x-b.x,a.y-b.y};
}
il LL dot(co Vector&a,co Vector&b){
    return a.x*b.x+a.y*b.y;
}
il LL cross(co Vector&a,co Vector&b){
    return a.x*b.y-a.y*b.x;
}

co int N=200000+10;
Vector p[N];
int main(){
    int n=read<int>();
    for(int i=1;i<=n;++i) read(p[i].x),read(p[i].y);
    sort(p+1,p+n+1),copy(p+1,p+n+1,p+n+1);
    
    LL ans=0;
    Vector now=p[1];
    for(int i=1,j=1;i<=n;++i){
        while(j+1<i+n&&cross(p[i],p[j+1])>=0) now=now+p[++j],ans=max(ans,dot(now,now));
        now=now-p[i],ans=max(ans,dot(now,now));
    }
    printf("%lld.000\n",ans);
    return 0;
}

欢乐的随机化

首先我们可以按极角排序。然后对y轴上方/下方的加起来分别求模长取个最大值。这样一次是 O(n) 的。

我们可以对所有向量每次随机化旋转一下,然后执行上面的过程。数据好像很水然后就艹过去了。

猜你喜欢

转载自www.cnblogs.com/autoint/p/randomization.html