【题解】毒瘤 OI 刷题汇总 [SCOI2012]

【题解】毒瘤 OI 刷题汇总 [SCOI2012]

由于不清楚题目顺序,就按照 \(\text{BZOJ}\) 上面的排列好了。


【Day1 T1】滑雪

传送门:滑雪 \(\text{[P2573]}\) \(\text{[Bzoj2753]}\)

【题目描述】

给出一个由 \(n\) \((n\leqslant 10^5)\) 个点、\(m\) \((m\leqslant 10^6)\) 条边组成的无向图,点和边均有权值,求以 \(1\) 为根的有向树形图,对于每条选出来的有向边 \((x,y)\) 必须满足 \(x\) 的权值大于等于 \(y\) 的权值,在包含点数最大的前提下,求出最大边权和。

【分析】

拓扑 + 最大生成树 乱搞。

首先是求能包含的最大点数,其实质就是从 \(1\) 出发走合法有向边能到达的点的个数,拓扑排序暴力统计即可。

在拓扑的过程中顺带把经过的边全部存起来,然后在这些边中选出一部分使得边权和最大,其实就是个最大生成树,\(kruscal\) 暴力搞搞就可以了。

时间复杂度:\(O(mlogm)\)

【Code】

#include<algorithm>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=1e5+3,M=1e6+3;
int n,m,o,x,y,z,h,t,cnt,Q[N],A[N],fa[N],vis[N],head[N];LL ans;
struct QAQ{int w,to,next;}a[M<<1];
struct QWQ{int x,y,z;inline bool operator<(const QWQ &O)const{return A[y]!=A[O.y]?A[y]>A[O.y]:z<O.z;};}B[M<<1];
inline void add(Re x,Re y,Re z){a[++o].w=z,a[o].to=y,a[o].next=head[x],head[x]=o;}
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline int find(Re x){return fa[x]==x?x:fa[x]=find(fa[x]);}
int main(){
//    freopen("456.txt","r",stdin);
    in(n),in(m);
    for(Re i=1;i<=n;++i)in(A[i]);
    while(m--){
        in(x),in(y),in(z);
        if(A[x]>=A[y])add(x,y,z);//注意权值相等时要连两条边
        if(A[y]>=A[x])add(y,x,z);
    }
    m=0,h=1,t=0,Q[++t]=1,vis[1]=1;
    while(h<=t){
        Re x=Q[h++];++cnt;
        for(Re i=head[x],to;i;i=a[i].next)
            if(A[x]>=A[to=a[i].to]){
                B[++m].x=x,B[m].y=to,B[m].z=a[i].w;
                if(!vis[to])vis[to]=1,Q[++t]=to;
            }
    }
    sort(B+1,B+m+1);
    for(Re i=1;i<=n;++i)fa[i]=i;
    for(Re i=1,t=0;i<=m&&t<cnt-1;++i)
        if((x=find(B[i].x))!=(y=find(B[i].y)))
            ans+=B[i].z,fa[x]=y,++t;
    printf("%d %lld\n",cnt,ans);
}

【Day1 T2】喵星球上的点名

传送门:喵星球上的点名 \(\text{[P2336]}\) \(\text{[Bzoj2754]}\)

【题目描述】

给定 \(n\) \((n\leqslant 5*10^4)\) 对字符串 \(a_i,b_i\) 表示喵咪的信息,以及 \(m\) 个询问串 \(s_j\) 。在一次询问中,若 \(s_j\)\(a_i\) 或者 \(b_i\) 的子串,则 \(i\) 会被统计一次。

对于每次询问输出统计到的喵咪个数,最后再对于每个喵咪,输出在 \(m\) 次询问中被统计的次数。

【分析】

字符串经典题,大部分高级字符串匹配算法都可以艹过。

由于我 菜+懒,只写了广义后缀自动机的做法。

其实用广义 \(\text{SAM}\) 的话这就是板子题,统计答案时暴力跳 \(parent\) 并染色,遇到已经染过的地方就跳过。

然后...就完了...

时间复杂度:\(O(n*\text{玄学})\)

听巨佬说暴跳 \(parent\) 可以被卡到根号,但如果用 \(dfs\) 序优化的话,能做到上界 \(O(n\log n)\)(不管这么多了,反正能过就行)。

【Code】

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#include<map>
#define Re register int
#define LL long long
using namespace std;
const int N=2e5+5;
int n,t,T,x,s[N],ch[N],Len[N];
inline void in(Re &x){
    int fu=0;x=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=fu?-x:x;
}
struct Suffix_Automaton{    
    int O,pos[N],cnt[N],link[N],maxlen[N],minlen[N];queue<int>Q;map<int,int>trans[N];
    Suffix_Automaton(){O=1;}
    inline int insert(Re ch,Re last){
        if(trans[last][ch]&&maxlen[last]+1==maxlen[trans[last][ch]])return trans[last][ch];
        Re x,y,z=++O,p=last,flag=0;maxlen[z]=maxlen[last]+1;
        while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];
        if(!p)link[z]=1;
        else{
            x=trans[p][ch];
            if(maxlen[p]+1==maxlen[x])link[z]=x;
            else{
                if(maxlen[p]+1==maxlen[z])flag=1;
                y=++O;maxlen[y]=maxlen[p]+1;
                trans[y]=trans[x];
                while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
                link[y]=link[x],link[z]=link[x]=y;
            }
        }
        return flag?y:z;
    }
    int co[N];
    inline void updata(Re p,Re id){
        while(p&&co[p]!=id)++cnt[p],co[p]=id,p=link[p];
    }
    int gs[N],Ans[N];
    inline void updata_(Re p,Re id){
        while(p&&co[p]!=id)Ans[id]+=gs[p],co[p]=id,p=link[p];
    }
    inline void ask(Re ch[],Re L){
        Re p=1,flag=1;
        for(Re i=1;i<=L&&flag;++i){
            Re a=ch[i];
            if(trans[p][a])p=trans[p][a];
            else flag=0;
        }
        if(flag)++gs[p];
        printf("%d\n",flag?cnt[p]:0);
    }
}SAM;
int main(){
//    freopen("123.txt","r",stdin);
    in(n),in(T);
    for(Re i=1;i<=n;++i){
        in(Len[(i<<1)-1]);Re last=1;
        for(Re j=1;j<=Len[(i<<1)-1];++j)in(x),last=SAM.insert(s[++t]=x,last);
        in(Len[i<<1]);last=1;
        for(Re j=1;j<=Len[i<<1];++j)in(x),last=SAM.insert(s[++t]=x,last);
    }
    for(Re i=1,t=0;i<=n;++i){
        for(Re j=1,p=1;j<=Len[(i<<1)-1];++j)
            SAM.updata(p=SAM.trans[p][s[++t]],i);
        for(Re j=1,p=1;j<=Len[i<<1];++j)
            SAM.updata(p=SAM.trans[p][s[++t]],i);
    }
    while(T--){
        in(Len[0]);
        for(Re i=1;i<=Len[0];++i)in(ch[i]);
        SAM.ask(ch,Len[0]);
    }
    memset(SAM.co,0,sizeof(SAM.co));
    for(Re i=1,t=0;i<=n;++i){
        for(Re j=1,p=1;j<=Len[(i<<1)-1];++j)
            SAM.updata_(p=SAM.trans[p][s[++t]],i);
        for(Re j=1,p=1;j<=Len[i<<1];++j)
            SAM.updata_(p=SAM.trans[p][s[++t]],i);
    }
    for(Re i=1;i<=n;++i)printf("%d ",SAM.Ans[i]);
}

【Day1 T3】喵星人入侵

传送门:喵星人入侵 \(\text{[P2337]}\) \(\text{[Bzoj2755]}\)

【题目描述】

略。

【分析】

又是毒瘤插头 \(dp\),不会,先咕着。

【Code】

不知道这儿能放啥,干脆买个萌吧(⊙ω⊙)

【Day2 T1】奇怪的游戏

传送门:奇怪的游戏 \(\text{[P5038]}\) \(\text{[Bzoj2756]}\)

【题目描述】

共有 \(T\) \((T\leqslant 10)\) 组数据,每组数据给出一个 \(n\times m\) \((n,m\leqslant 40)\) 的棋盘,每个位置 \((i,j)\) 上的数为 \(v[i][j]\)

一次操作可以选择两个相邻的数,并使这数都加上 \(1\),求最少需要多少次操作才能使棋盘中的所有数都相同,如果无解输出 \(-1\)

【分析】

二分 + 最大流判断可行性。

注意到每次操作都是选择两个相邻的位置,可以先对棋盘黑白染色,建出二分图。

设黑点、白点分别有 \(A,B\) 个,其权值总和分别为 \(a,b\),设操作次数为 \(k\),最后所有数字都变成了 \(X\),由于每次操作都会使黑点总权值增加 \(1\),则有:\(a+k=A*X\),同理得 \(b+k=B*X\),联立得 \(k=A*X-a=B*X-b\),即 \((A-B)*X=a-b\)

\(A\neq B\) 时,可以直接算出 \(X\),但这个 \(X\) 可能并不合法,所以还要跑最大流 \(judge\) 一下。

\(A=B\) 时,此时如果 \(a\neq b\) 则说明一定无解(每次操作并不会改变 \(a,b\) 的相对大小,\(a,b\) 永远都不能相等)。
由于黑白点个数相等,可知 \(n*m\) 为偶数,如果某一个 \(X\) 是可行的,那么进行 \(\frac{n*m}{2}\) 次操作可以将所有数都变为 \(X+1\),即说明 \(X+1\) 也一定是可行的。所以这个 \(X\) 是可以二分,每次跑最大流判断可行性即可。

关于如何跑最大流 \(judge\)

在已经确定目标值 \(X\) 的情况下,每个位置 \((i,j)\) 上的点需要被操作 \(X-v[i][j]\) 次。

分别设立超源、超汇,超源向所有白点连容量为 \(X-v[i][j]\) 的边,所有黑点向超汇连容量为 \(X-v[i][j]\) 的边,所有白点到相邻的黑点连容量为 \(inf\) 的边,跑一遍 \(Dinic\) 算法,如果最大流 \(maxflow=\sum_{(i,j)\in\{\text{白点}\}}(X-v[i][j])\),则说明 \(X\) 是可行的。

时间复杂度:\(O(\log (inf) nm\sqrt{nm})\),带一个取决于建边数量的常数。

【Code】

#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=1600+5,M=N*3+5;//点数:N=nm 边数:M=N/2*4+N/2*2=3N
const LL inf=1e18;
int n,m,T,st,ed;
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
struct Dinic{
    int o,h,t,Q[N],cur[N],dis[N],head[N];LL maxflow;
    struct QAQ{int to,next;LL flow;}a[M<<1];
    inline void add_(Re x,Re y,LL flow){a[++o].to=y,a[o].flow=flow,a[o].next=head[x],head[x]=o;}
    inline void add(Re x,Re y,LL flow){add_(x,y,flow),add_(y,x,0);}
    inline void CL(){memset(head,0,sizeof(head)),o=1.,maxflow=0;}
    inline int bfs(Re st,Re ed){
        for(Re i=0;i<=ed;++i)dis[i]=0,cur[i]=head[i];
        h=1,t=0,dis[Q[++t]=st]=1;
        while(h<=t){
            Re x=Q[h++];
            for(Re i=head[x],to;i;i=a[i].next)
                if(a[i].flow&&!dis[to=a[i].to]){
                    dis[to]=dis[x]+1,Q[++t]=to;
                    if(to==ed)return 1;
                }
        }
        return 0;
    }
    inline LL dfs(Re x,LL flow){
        if(x==ed||!flow)return flow;
        LL tmp=0,f;
        for(Re i=cur[x],to;i;i=a[i].next){
            cur[x]=i;
            if(dis[to=a[i].to]==dis[x]+1&&(f=dfs(to,min(a[i].flow,flow-tmp)))){
                a[i].flow-=f,a[i^1].flow+=f,tmp+=f;
                if(tmp==flow)break;
            }
        }
        return tmp;
    }
    inline void dinic(Re st,Re ed){while(bfs(st,ed))maxflow+=dfs(st,inf);}
}T1;
int cnt1,cnt2,MaxA,A[43][43],wx[4]={0,0,1,-1},wy[4]={1,-1,0,0};LL sp1,sp2;
inline int Poi(Re i,Re j){return (i-1)*m+j;}
inline int judge(LL X){
    LL S=0;T1.CL();
    for(Re i=1;i<=n;++i)
        for(Re j=1;j<=m;++j)
            if((i+j)&1)T1.add(Poi(i,j),ed,X-A[i][j]);
            else{
                T1.add(st,Poi(i,j),X-A[i][j]),S+=X-A[i][j];
                for(Re k=0;k<4;++k){
                    Re x=i+wx[k],y=j+wy[k];
                    if(x>=1&&x<=n&&y>=1&&y<=m)T1.add(Poi(i,j),Poi(x,y),inf);
                }
            }
    T1.dinic(st,ed);
    return T1.maxflow==S;
}
int main(){
//    freopen("123.txt","r",stdin);
    in(T);
    while(T--){
        in(n),in(m),st=n*m+1,ed=st+1,MaxA=cnt1=cnt2=sp1=sp2=0;
        for(Re i=1;i<=n;++i)
            for(Re j=1;j<=m;++j){
                in(A[i][j]),MaxA=max(MaxA,A[i][j]);
                if((i+j)&1)sp1+=A[i][j],++cnt1;
                else sp2+=A[i][j],++cnt2;
            }
        if(cnt1!=cnt2){
            LL X=(sp1-sp2)/(cnt1-cnt2);
            if(X>=MaxA&&judge(X))printf("%lld\n",X*cnt1-sp1);
            else puts("-1");
        }
        else{
            if(sp1!=sp2){puts("-1");continue;}
            LL r=inf,l=MaxA;
            while(l<r){
                LL mid=l+r>>1;
                LL tmp=l+r>>1;
                if(judge(mid))r=mid;
                else l=mid+1;
            }
            printf("%lld\n",r==inf?-1:r*cnt1-sp1);
        }
    }
}

【Day2 T2】Blinker 的仰慕者

传送门:\(\text{Blinker}\) 的仰慕者 \(\text{[P5842]}\) \(\text{[Bzoj2757]}\)

【题目描述】

【分析】

又是毒瘤数位 \(dp\),不会,先咕着。

【Code】

不知道这儿能放啥,干脆买个萌吧(⊙ω⊙)

【Day2 T3】Blinker 的噩梦

传送门:\(\text{Blinker}\) 的噩梦 \(\text{[P5843]}\) \(\text{[Bzoj2758]}\)

【题目描述】

略。

【分析】

毒瘤 \(OI\) 的毒瘤计算几何终于开始显示其毒瘤本质了。

自己随便瞎造的数据 把网上仅有的几篇题解都给卡了,先咕着吧。。。

【Code】

不知道这儿能放啥,干脆买个萌吧(⊙ω⊙)

posted @ 2020-04-16 09:59  辰星凌  阅读(242)  评论(0编辑  收藏  举报