zoukankan      html  css  js  c++  java
  • [2020.2.19]codeforces1307 Codeforces Round #621 (Div. 1 + Div. 2)

    回想起上次写博客,好像已经是去年的事一样

    概况

    排名:179/7155

    过题数:5

    Rating:(color{green}{+74})((color{orange}{2263}))

    题目

    A. Cow and Haybales

    AC时间:4min,492分

    题解:

    显然先把所有在位置2的移到1,再把在位置3的移到一,以此类推直到天数不足。

    直接模拟即可。

    code:

    #include<bits/stdc++.h>
    using namespace std;
    int T,n,d,a[110],ans,t;
    int main(){
    	scanf("%d",&T);
    	while(T--){
    		scanf("%d%d",&n,&d),ans=0;
    		for(int i=1;i<=n;++i)scanf("%d",&a[i]),t=min(a[i],i!=1?d/(i-1):100),ans+=t,d-=(i-1)*t;
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    B.Cow and Friend

    AC时间:12min,952分

    题解:

    注意到两步可以在横轴上跳出(0)(2max(a_i))的任意距离。

    那么答案至多是(lceilfrac{x}{2max(a_i)} ceil)

    或者答案为奇数,那么就是(lceilfrac{x-max(a_i)}{2max(a_i)} ceil+1)

    比赛代码写得太zz,就不放了。

    C.Cow and Message

    AC时间:21min,1374分

    题解:

    其实secret message长度一定是1或2,因为任何长度大于2的子串出现次数都小于等于其长度为2的前缀。

    于是考虑如何计算一个长度为2的字符串的出现次数。

    枚举第一个字符,然后顺序枚举字符串。记录之前第一个字符出现的次数,在第(i)位将以(s_i)为结尾的串数量加上之前第一个字符的出现次数即可。

    code:

    #include<bits/stdc++.h>
    using namespace std;
    int n,apn;
    long long ans,tot[30];
    char s[100010];
    int main(){
    	scanf("%s",s+1),n=strlen(s+1);
    	for(int i=0;i<26;++i){
    		apn=0;
    		for(int j=0;j<26;++j)tot[j]=0;
    		for(int j=1;j<=n;++j)tot[s[j]-'a']+=apn,apn+=(s[j]-'a'==i);
    		for(int j=0;j<26;++j)ans=max(ans,tot[j]);
    		ans=max(ans,1ll*apn);
    	}
    	printf("%I64d",ans);
    	return 0;
    }
    

    D.Cow and Fields

    AC时间:54min,1518分(-1)

    题解:

    两遍bfs求出每个点与(1)(n)的距离,记作(d_{1,i})(d_{n,i})

    那么如果我们在(u,v)之间连边,最短路会对(min(d_{1,u}+d_{n,v},d_{n,u}+d_{1,v})+1)(min)

    于是我们将特殊点到(1,n)的距离分别排序,然后二分判断最短路长度(ge x)是否可行。

    假设以到(1)的距离和到(n)的距离从小到大排序后的特殊点分别为(a_1,a_2,...,a_k)(b_1,b_2,...,b_k)

    那么对于任何一个(a_i),使得(d_{1,a_i}+d_{n,b_j}+1ge x)(j)一定是一段后缀,而且如果(i_1le i_2),那么(i_1)所对应的后缀一定不短于(i_2)所对应的后缀。

    那么我们倒序枚举(a_i),同时维护所对应后缀中,(d_{1,b_i})的最大值和次大值。

    然后如果存在(a_i),使得(d_{n,a_i}+mxd_i+1ge x),那么最短路长度为(x)可行。其中若后缀最大值取到(d_{1,a_i}),那么(mxd_i)为后缀次大值,否则为最大值。

    code:

    #include<bits/stdc++.h>
    #define ci const int&
    using namespace std;
    struct edge{
    	int t,nxt;
    }e[400010];
    struct Val{
    	int dis,id;
    }t1[200010],tn[200010];
    int n,m,k,a[200010],u,v,t,be[200010],cnt,d1[200010],dn[200010],f1,v1,v2,cv,l,r,mid;
    queue<int>q;
    void add(ci x,ci y){
    	e[++cnt].t=y,e[cnt].nxt=be[x],be[x]=cnt;
    }
    bool cmp(Val x,Val y){
    	return x.dis>y.dis;
    }
    void bfs(int*dis,ci st){
    	dis[st]=1,q.push(st);
    	while(!q.empty()){
    		t=q.front(),q.pop();
    		for(int i=be[t];i;i=e[i].nxt)!dis[e[i].t]?q.push(e[i].t),dis[e[i].t]=dis[t]+1:0;
    	}
    	for(int i=1;i<=n;++i)--dis[i];
    }
    bool Check(ci x){
    	int tl=0;
    	f1=0,v1=v2=-1;
    	for(int i=k;i>=1;--i){
    		while(tl<k&&t1[i].dis+tn[tl+1].dis+1>=x){
    			++tl,t=tn[tl].id;
    			if(d1[t]>v1)v2=v1,v1=d1[t],f1=t;
    			else if(d1[v]>v2)v2=d1[v];
    		}
    		if(!tl||(tl==1&&f1==t1[i].id))continue;
    		cv=(f1==t1[i].id?v2:v1);
    		if(dn[t1[i].id]+cv+1>=x)return true;
    	}
    	return false;
    }
    int main(){
    	scanf("%d%d%d",&n,&m,&k);
    	for(int i=1;i<=k;++i)scanf("%d",&a[i]);
    	for(int i=1;i<=m;++i)scanf("%d%d",&u,&v),add(u,v),add(v,u);
    	bfs(d1,1),bfs(dn,n);
    	for(int i=1;i<=k;++i)t1[i]=(Val){d1[a[i]],a[i]},tn[i]=(Val){dn[a[i]],a[i]};
    	sort(t1+1,t1+k+1,cmp),sort(tn+1,tn+k+1,cmp);
    	l=1,r=d1[n];
    	while(l<r)mid=l+r+1>>1,Check(mid)?l=mid:r=mid-1;
    	printf("%d",l);
    	return 0;
    }
    

    E.Cow and Treats

    AC时间:2h5min,1200分

    题解:

    首先,喜好相同的牛至多只能出现2头,而且若出现了两头,一定在不同的方向上,也就是一个方向上的牛一定是喜好互不相同的,也就是说每一头牛吃哪些草是固定的。

    如果一头牛需要的草的数量大于这种草的总量,可以直接将其删除。

    同时,容易发现对于一种将一些牛分入左右两个集合的方式,使其合法的排队方式要么不存在,要么唯一。

    所以我们可以不考虑牛之间的相互顺序,只考虑它是否被加入集合,以及在左边还是右边。

    那么,我们对于每一头牛预处理其从左往右和从右往左时会在哪里睡下,记为(pl,pr)

    然后,我们枚举从左向右走的牛最远走到了哪里,记为(r),计算此时最多的牛的数量和方案数。

    我们再枚举牛的种类,然后分类讨论:

    如果这种牛正好喜欢(r)处的草,那么如果不存在一头牛使得(pl=r),那么显然无解。

    否则考虑是否存在(pl eq r,pr>r)的牛,如果不存在,最多牛的数量+1,方案数不变。

    如果存在,最多牛的数量+2,方案数乘上这种牛的数量。

    然后考虑这种牛不喜欢(r)处的草,如果不存在(pl<r)(pr>r)的牛,那么这种牛不会对答案造成任何贡献。

    如果不能找出两头不同的牛,使得第一头满足(pl<r)且第二头满足(pr>r),那么最多牛的数量+1,方案数乘上满足(pl<r)的牛的数量和(pr>r)的牛的数量的和。

    否则,最多牛的数量+2,记上一次的方案数为(lst),然后枚举那一头牛是第一头牛,答案加上(lst imes)能够作为第二头牛的牛数(也就是除去第一头牛外满足(pr>r)的牛的数量)。

    最后,还要考虑没有牛在左侧的情况。比较简单就不讲了。

    code:

    #include<bits/stdc++.h>
    #define ci const int&
    using namespace std;
    const int mod=1e9+7;
    struct cw{
    	int sl,sr;
    }tmp;
    int n,m,u,v,s[5010],wy[2],tg,tt,numc,tag,ans1,ans2;
    vector<cw>c[5010];
    bool cmp(cw x,cw y){
    	return x.sl<y.sl;
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i)scanf("%d",&s[i]);
    	for(int i=1;i<=m;++i){
    		scanf("%d%d",&u,&v);
    		tmp.sl=1,tmp.sr=n,tt=0;
    		while(tmp.sl<=n&&(tt+=(s[tmp.sl]==u))<v)++tmp.sl;
    		if(tmp.sl>n)continue;
    		tt=0;
    		while(tmp.sr>=1&&(tt+=(s[tmp.sr]==u))<v)--tmp.sr;
    		if(tmp.sr<1)continue;
    		c[u].push_back(tmp);
    	}
    	for(int i=1;i<=n;++i)if(c[i].size())sort(c[i].begin(),c[i].end(),cmp);
    	for(int i=1;i<=n;++i)if(c[s[i]].size()&&c[s[i]][0].sl<=i){
    		tt=0,wy[tg]=1;
    		for(int j=1;j<=n&&wy[tg];++j)if(c[j].size()){
    			tg^=1,tag=numc=wy[tg]=0;
    			if(j!=s[i]){
    				for(int k=0;k<c[j].size();++k)numc+=(c[j][k].sr>i);
    				if(!numc&&c[j][0].sl>i){
    					tg^=1;
    					continue;
    				}
    				++tt;
    				for(int k=0;k<c[j].size()&&c[j][k].sl<i;++k){
    					if(!tag&&numc-(c[j][k].sr>i))tag=1,wy[tg]=0;
    					wy[tg]=(wy[tg]+1ll*wy[tg^1]*max(1,numc-(c[j][k].sr>i)))%mod;
    				}
    				if(!tag)wy[tg]=(wy[tg]+1ll*wy[tg^1]*numc)%mod;
    				tt+=tag;
    			}else{
    				++tt;
    				for(int k=0;k<c[j].size();++k)numc+=(c[j][k].sr>i&&c[j][k].sl!=i),tag|=(c[j][k].sl==i);
    				if(!tag)goto End;
    				wy[tg]=1ll*wy[tg^1]*max(1,numc)%mod,tt+=!!numc;
    			}
    		}
    		if(!wy[tg]||tt<ans1)continue;
    		if(tt>ans1)ans1=tt,ans2=wy[tg];
    		else if(tt==ans1)(ans2+=wy[tg])>=mod?ans2-=mod:0;
    		End:;
    	}
    	//NO L
    	tt=0,wy[tg]=1;
    	for(int i=1;i<=n;++i)if(c[i].size())++tt,wy[tg]=1ll*wy[tg]*c[i].size()%mod;
    	if(tt>ans1)ans1=tt,ans2=wy[tg];
    	else if(tt==ans1)(ans2+=wy[tg])>=mod?ans2-=mod:0;
    	printf("%d %d",ans1,ans2);
    	return 0;
    }
    

    G.Cow and Exercise(赛后)

    题解:

    考虑对于每组询问二分答案(mid),计算此时最少需要多少代价。

    我们可以给每一个点一个值(d_u),使得(dis(1,u)=d_u-d_1)

    那么根据最短路的性质,对于每条边((u,v))我们有

    (d_vle d_u+w_{u,v}+x_{u,v}),其中(w_{u,v})为边((u,v))一开始的边权,(x_{u,v})为我们加的边权。

    同时我们有(d_n-d_1ge mid),然后我们要最小化(sum_{(u,v)}x_{u,v})

    经过简单的转化,我们有

    (x_{u,v}+d_u-d_vge-w_{u,v})

    (d_n-d_1ge mid)

    然后发现这个问题的对偶问题为一个最大费用循环流问题,即:

    (u,v)之间连上容量为(1),费用为(-w_{u,v})的边,在(t,1)之间连上容量没有限制,费用为(mid)的边,求最大费用循环流。

    但是如果对于每一个mid都建图跑网络流显然是要T的。

    我们发现求这个网络的最大费用循环流相当于:

    (u,v)之间连上容量为(1),费用为(w_{u,v})的边,在这个图中求一个流,设流量为(f),费用为(c),求(mid imes f-c)的最大值。

    于是,我们考虑对于每一个(f)求出达到这个流量的最小费用。

    参考最小费用最大流的过程,若我们以(c)为横坐标,最小的(f)为纵坐标,图像应该长成一个连续的分段函数,每一段都是一个一次函数,第(i)段的斜率正是第(i)次增广的路径的费用,同时该斜率是不降的。

    于是我们对这个网络跑一次费用流,记录每次增广的单位花费(cost_i),此时网络的总流量(f_i),此时的总花费(C_i)。对于每个(mid),可以直接找到最大的(cost_i<mid),此时的(mid imes f_i-C_i)就是最大费用循环流,也即使原图最短路长度不小于(mid)的最小花费。

    code:

    #include<bits/stdc++.h>
    #define REV(x) (x&1?x+1:x-1)
    #define ci const int&
    using namespace std;
    const int INF=1e9;
    struct edge{
    	int t,nxt,f,c;
    }e[5010];
    int n,m,Q,u,v,w,be[55],cnt,sz,fv[2510],fl[2510],dis[55],iq[55],ls[55],fe[55],t,lf;
    double l,r,mid;
    long long sv[2510];
    queue<int>q;
    void add(ci x,ci y,ci fl,ci cs){
    	e[++cnt]=(edge){y,be[x],fl,cs},be[x]=cnt;
    }
    bool SPFA(){
    	for(int i=2;i<=n;++i)dis[i]=INF;
    	q.push(1),iq[1]=1;
    	while(!q.empty()){
    		iq[t=q.front()]=0,q.pop();
    		for(int i=be[t];i;i=e[i].nxt)if(e[i].f&&dis[e[i].t]>dis[t]+e[i].c){
    			dis[e[i].t]=dis[t]+e[i].c,ls[e[i].t]=t,fe[e[i].t]=i;
    			if(!iq[e[i].t])q.push(e[i].t),iq[e[i].t]=1;
    		}
    	}
    	if(dis[n]>=INF)return false;
    	t=0,lf=INF;
    	for(int i=n;i!=1;i=ls[i])lf=min(lf,e[fe[i]].f);
    	for(int i=n;i!=1;i=ls[i])e[fe[i]].f-=lf,e[REV(fe[i])].f+=lf,t+=e[fe[i]].c;
    	if(!sz||fv[sz]!=t)fv[++sz]=t,sv[sz]=sv[sz-1]+lf*t,fl[sz]=fl[sz-1]+lf;
    	else sv[sz]+=lf*t,fl[sz]+=lf;
    	return true;
    }
    bool Check(double x){
    	int tl=1,tr=sz,tm;
    	while(tl<tr)tm=tl+tr+1>>1,fv[tm]<x?tl=tm:tr=tm-1;
    	return fl[tl]*x-sv[tl]<=w;
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=m;++i)scanf("%d%d%d",&u,&v,&w),add(u,v,1,w),add(v,u,0,-w);
    	while(SPFA());
    	scanf("%d",&Q);
    	while(Q--){
    		scanf("%d",&w);
    		l=1,r=1e26;
    		while(r-l>1e-7)mid=(l+r)/2.0,Check(mid)?l=mid:r=mid;
    		printf("%.10lf
    ",l);
    	}
    	return 0;
    }
    

    总结

    现场做出G就赢了/kk

    但当时并不懂循环流那套理论

    所以还要再学习一个(

  • 相关阅读:
    MongoDB + Spark: 完整的大数据解决方案
    07对象字面量
    05JavaScript中数组的使用
    04JavaScript中函数也是对象
    03JavaScript中的函数预解析
    02通过arguments实现方法重载
    01函数重名问题
    mxGraph 学习笔记 --mxGraph常用功能代码
    mxGraph学习笔记--设置节点鼠标事件
    mxGraph 学习笔记 --右键菜单
  • 原文地址:https://www.cnblogs.com/xryjr233/p/CF1307.html
Copyright © 2011-2022 走看看