[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
但当时并不懂循环流那套理论
所以还要再学习一个(