【题解】毒瘤 OI 刷题汇总 [SCOI2012]
由于不清楚题目顺序,就按照 ( ext{BZOJ}) 上面的排列好了。
【Day1 T1】滑雪
传送门:滑雪 ( ext{[P2573]}) ( ext{[Bzoj2753]})
【题目描述】
给出一个由 (n) ((nleqslant 10^5)) 个点、(m) ((mleqslant 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
",cnt,ans);
}
【Day1 T2】喵星球上的点名
传送门:喵星球上的点名 ( ext{[P2336]}) ( ext{[Bzoj2754]})
【题目描述】
给定 (n) ((nleqslant 5*10^4)) 对字符串 (a_i,b_i) 表示喵咪的信息,以及 (m) 个询问串 (s_j) 。在一次询问中,若 (s_j) 是 (a_i) 或者 (b_i) 的子串,则 (i) 会被统计一次。
对于每次询问输出统计到的喵咪个数,最后再对于每个喵咪,输出在 (m) 次询问中被统计的次数。
【分析】
字符串经典题,大部分高级字符串匹配算法都可以艹过。
由于我 菜+懒,只写了广义后缀自动机的做法。
其实用广义 ( ext{SAM}) 的话这就是板子题,统计答案时暴力跳 (parent) 并染色,遇到已经染过的地方就跳过。
然后...就完了...
时间复杂度:(O(n* ext{玄学})) 。
听巨佬说暴跳 (parent) 可以被卡到根号,但如果用 (dfs) 序优化的话,能做到上界 (O(nlog 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
",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】喵星人入侵
传送门:喵星人入侵 ( ext{[P2337]}) ( ext{[Bzoj2755]})
【题目描述】
略。
【分析】
又是毒瘤插头 (dp),不会,先咕着。
【Code】
不知道这儿能放啥,干脆买个萌吧(⊙ω⊙)
【Day2 T1】奇怪的游戏
传送门:奇怪的游戏 ( ext{[P5038]}) ( ext{[Bzoj2756]})
【题目描述】
共有 (T) ((Tleqslant 10)) 组数据,每组数据给出一个 (n imes m) ((n,mleqslant 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 eq B) 时,可以直接算出 (X),但这个 (X) 可能并不合法,所以还要跑最大流 (judge) 一下。
当 (A=B) 时,此时如果 (a
eq 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{ ext{白点}}}(X-v[i][j])),则说明 (X) 是可行的。
时间复杂度:(O(log (inf) nmsqrt{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
",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
",r==inf?-1:r*cnt1-sp1);
}
}
}
【Day2 T2】Blinker 的仰慕者
传送门:( ext{Blinker}) 的仰慕者 ( ext{[P5842]}) ( ext{[Bzoj2757]})
【题目描述】
略
【分析】
又是毒瘤数位 (dp),不会,先咕着。
【Code】
不知道这儿能放啥,干脆买个萌吧(⊙ω⊙)
【Day2 T3】Blinker 的噩梦
传送门:( ext{Blinker}) 的噩梦 ( ext{[P5843]}) ( ext{[Bzoj2758]})
【题目描述】
略。
【分析】
毒瘤 (OI) 的毒瘤计算几何终于开始显示其毒瘤本质了。
自己随便瞎造的数据 把网上仅有的几篇题解都给卡了,先咕着吧。。。
【Code】
不知道这儿能放啥,干脆买个萌吧(⊙ω⊙)