[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\n",ans);
	}
	return 0;
}

B.Cow and Friend

AC时间:12min,952分

题解:

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

那么答案至多是\(\lceil\frac{x}{2\max(a_i)}\rceil\)

或者答案为奇数,那么就是\(\lceil\frac{x-\max(a_i)}{2\max(a_i)}\rceil+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}+1\ge x\)\(j\)一定是一段后缀,而且如果\(i_1\le i_2\),那么\(i_1\)所对应的后缀一定不短于\(i_2\)所对应的后缀。

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

然后如果存在\(a_i\),使得\(d_{n,a_i}+mxd_i+1\ge 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\neq r,pr>r\)的牛,如果不存在,最多牛的数量+1,方案数不变。

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

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

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

否则,最多牛的数量+2,记上一次的方案数为\(lst\),然后枚举那一头牛是第一头牛,答案加上\(lst\times\)能够作为第二头牛的牛数(也就是除去第一头牛外满足\(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_v\le d_u+w_{u,v}+x_{u,v}\),其中\(w_{u,v}\)为边\((u,v)\)一开始的边权,\(x_{u,v}\)为我们加的边权。

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

经过简单的转化,我们有

\(x_{u,v}+d_u-d_v\ge-w_{u,v}\)

\(d_n-d_1\ge mid\)

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

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

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

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

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

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

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

于是我们对这个网络跑一次费用流,记录每次增广的单位花费\(cost_i\),此时网络的总流量\(f_i\),此时的总花费\(C_i\)。对于每个\(mid\),可以直接找到最大的\(cost_i<mid\),此时的\(mid\times 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\n",l);
	}
	return 0;
}

总结

现场做出G就赢了/kk

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

所以还要再学习一个(

posted @ 2020-02-19 21:34  xryjr233  阅读(293)  评论(0编辑  收藏  举报