AtCoder Grand Contest 034
A:如果C在D左侧,显然先让B到达终点再让A走即可,否则先判断一下A是否可以在某处超过B。也就是先判断一下起点与终点之间是否有连续的障碍,若有则无解;然后若C在D左侧输出Yes,否则判断B和D之间是否有存在某个空地其左右均为空地,若有则输出Yes,否则输出No。
#include<bits/stdc++.h> using namespace std; #define ll long long #define inf 1000000010 #define N 200010 char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,a,b,c,d; char s[N]; signed main() { n=read(),a=read(),b=read(),c=read(),d=read(); scanf("%s",s+1); for (int i=1;i<n;i++) if (s[i]=='#'&&s[i+1]=='#'&&(a<=i&&c>i||b<=i&&d>i)) {cout<<"No";return 0;} if (c>d) { int pos=-1; for (int i=b;i<=d;i++) if (s[i-1]=='.'&&s[i]=='.'&&s[i+1]=='.') {pos=i;break;} if (pos==-1) {cout<<"No";return 0;} else cout<<"Yes"; } else cout<<"Yes"; return 0; //NOTICE LONG LONG!!!!! }
B:考虑每个BC能与哪些A一起做出贡献,显然这样的A应在该BC前面,且它们之间只有A和连续出现的BC出现。
#include<bits/stdc++.h> using namespace std; #define ll long long #define inf 1000000010 #define N 200010 char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n; char s[N]; signed main() { int cnt=0;ll ans=0; scanf("%s",s+1);n=strlen(s+1); for (int i=1;i<=n;i++) if (s[i]=='A') cnt++; else if (s[i]=='B') { if (s[i+1]=='C') i++,ans+=cnt; else cnt=0; } else cnt=0; cout<<ans; return 0; //NOTICE LONG LONG!!!!! }
C:首先显然的是每门课权重要么取max要么取min,其中学的比对方多时取max,比对方少时取min。
然后注意到最多只有一门课不学满,其它课要么学满要么不学,因为假设最优解中有超过一门课不学满,显然将其中权重低的改为权重高的不会使答案更劣。
假设固定了某一门课不学满及学多少,对于剩余的课,将学满获得的收益减去不学获得的减益定义为价值,按价值从高到低依次选择学满即可。
这个过程可以改为先按价值排序,求出假设要么不学要么学满,最优要学满哪些课程才能满足条件,然后原问题的最优解显然是从这些学满的课程中删去一个再学习某个课程。
如果删去的不是被选中的课程中价值最低的,最优方案一定不会是选择其他课程,因为显然不优;而如果是价值最低的,可以枚举所有其他课程进行选择。先二分一下答案就好了。
#include<bits/stdc++.h> using namespace std; #define ll long long #define inf 1000000010 #define N 100010 #define int long long char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,m,pos; ll ans,s[N],tot; struct data { int x,l,r; bool operator <(const data&a) const { return 1ll*(m-x)*r+1ll*x*l>1ll*(m-a.x)*a.r+1ll*a.x*a.l; } }a[N]; ll mxval(int i){return 1ll*(m-a[i].x)*a[i].r;} ll val(int i,int k){if (k<a[i].x) return 1ll*(k-a[i].x)*a[i].l;else return 1ll*(k-a[i].x)*a[i].r;} bool check(int k,ll tot) { for (int i=1;i<pos;i++) if (tot-mxval(i)+val(i,k)>=0) return 1; tot-=mxval(pos);tot+=val(pos,0); for (int i=pos;i<=n;i++) if (tot-val(i,0)+val(i,k)>=0) return 1; return 0; } signed main() { n=read(),m=read(); for (int i=1;i<=n;i++) a[i].x=read(),a[i].l=read(),a[i].r=read(); sort(a+1,a+n+1); for (int i=n;i>=1;i--) s[i]=s[i+1]-val(i,0); tot=0;pos=0; for (int i=1;i<=n;i++) { tot+=mxval(i); if (tot>=s[i+1]) {pos=i;break;} } if (pos==0) {cout<<0;return 0;} tot-=s[pos+1]; int l=0,r=m; while (l<=r) { int mid=l+r>>1; if (check(mid,tot)) ans=mid,r=mid-1; else l=mid+1; } cout<<ans+1ll*(pos-1)*m; return 0; //NOTICE LONG LONG!!!!! }
D:直接暴力跑费用流边的数量过多。注意到|x1-x2|+|y1-y2|=max{(x1+y1)+(-x2-y2),(x1-y1)+(-x2+y2),(-x1+y1)+(x2-y2),(-x1-y1)+(x2+y2)},于是可以看成是给每个点在四种颜色中选择一种颜色,不同点的不同颜色价值不同,要求两组点中四种颜色出现次数均相同,最大化价值和。这个东西可以费用流,即两排点分别对应两组点,中间四个点表示四种颜色,源汇对应向两组点连边,容量为该坐标对应的点的数量,费用为0;两组点分别向四种颜色连边,容量inf,费用为该点取该颜色时的价值的相反数。这样边数就是O(n)的,跑费用流即可。
#include<bits/stdc++.h> using namespace std; #define ll long long #define inf 1000000010 #define N 2010 #define S 0 #define T 2001 #define red(x) (x) #define blue(x) (n+(x)) #define id(x) (2002+(x)) #define int long long char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,p[N],d[N],q[N],pre[N],t=-1,ans; bool flag[N]; struct data{int x,y,c,v[4]; }a[N],b[N]; struct data2{int to,nxt,cap,flow,cost; }edge[N<<5]; void addedge(int x,int y,int z,int cost) { t++;edge[t].to=y,edge[t].nxt=p[x],edge[t].cap=z,edge[t].flow=0,edge[t].cost=cost,p[x]=t; t++;edge[t].to=x,edge[t].nxt=p[y],edge[t].cap=0,edge[t].flow=0,edge[t].cost=-cost,p[y]=t; } int inc(int &x){x++;if (x>2007) x-=2007;return x;} bool spfa() { memset(d,60,sizeof(d));d[S]=0; memset(flag,0,sizeof(flag)); int head=0,tail=1;q[1]=S; do { int x=q[inc(head)];flag[x]=0; for (int i=p[x];~i;i=edge[i].nxt) if (edge[i].flow<edge[i].cap&&d[x]+edge[i].cost<d[edge[i].to]) { d[edge[i].to]=d[x]+edge[i].cost; pre[edge[i].to]=i; if (!flag[edge[i].to]) q[inc(tail)]=edge[i].to,flag[edge[i].to]=1; } }while (head!=tail); return d[T]<10000000000000000ll; } void ekspfa() { while (spfa()) { int v=n; for (int i=T;i!=S;i=edge[pre[i]^1].to) v=min(v,edge[pre[i]].cap-edge[pre[i]].flow); for (int i=T;i!=S;i=edge[pre[i]^1].to) edge[pre[i]].flow+=v,edge[pre[i]^1].flow-=v,ans+=v*edge[pre[i]].cost; } } signed main() { n=read();memset(p,255,sizeof(p)); for (int i=1;i<=n;i++) { a[i].x=read(),a[i].y=read(),a[i].c=read(); a[i].v[0]=a[i].x+a[i].y; a[i].v[1]=a[i].x-a[i].y; a[i].v[2]=-a[i].x+a[i].y; a[i].v[3]=-a[i].x-a[i].y; } for (int i=1;i<=n;i++) { b[i].x=read(),b[i].y=read(),b[i].c=read(); b[i].v[0]=-b[i].x-b[i].y; b[i].v[1]=-b[i].x+b[i].y; b[i].v[2]=b[i].x-b[i].y; b[i].v[3]=b[i].x+b[i].y; } for (int i=1;i<=n;i++) { addedge(S,red(i),a[i].c,0),addedge(blue(i),T,b[i].c,0); for (int j=0;j<4;j++) addedge(red(i),id(j),a[i].c,-a[i].v[j]),addedge(id(j),blue(i),b[i].c,-b[i].v[j]); } ekspfa(); cout<<-ans; return 0; //NOTICE LONG LONG!!!!! }
E:枚举一个根,然后O(n)check。显然将两个点一个往上拉一个往下拉没有任何意义,所以如果有解答案就为所有棋子的深度之和/2。于是只要判断是否有解。
同样显然的一点是对于根来说可以在其各儿子子树内部操作完后再进行子树间的操作,这样不会造成负面影响。于是可以划分成子问题了。设f[i]为i子树内部移动后深度之和的最小值,d[i]为i子树中所有棋子的深度之和,显然内部操作过程中f[i]~d[i]之间所有与d[i]奇偶性相同的深度之和都能被取到。f[root]=0时即有解。
考虑转移,先将f[son]和d[son]变为以i为根情况下的值,然后相当于要对每个儿子决定一个f[]~d[]之间变量x[](可以和d[]奇偶性不同,因为子树间可以相互匹配,不考虑奇偶性产生的误差至多为1,而实际上这不造成影响),最小化max{x[son]}*2-Σx[son]。当然这个值还要对d[i]%2取max。假设固定了max{x},要最小化该值,显然其他变量的取值均应为min(x,d[])。容易发现max{x}取d[]中的次大值时最优。
#include<bits/stdc++.h> using namespace std; #define ll long long #define inf 1000000010 #define N 2010 char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,p[N],a[N],t,deep[N],size[N],sigmadeep[N],f[N],root,ans=inf; struct data{int to,nxt; }edge[N<<1]; void addedge(int x,int y){t++;edge[t].to=y,edge[t].nxt=p[x],p[x]=t;} void dfs(int k,int from) { size[k]=a[k];sigmadeep[k]=a[k]*deep[k]; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from) { deep[edge[i].to]=deep[k]+1; dfs(edge[i].to,k); size[k]+=size[edge[i].to]; sigmadeep[k]+=sigmadeep[edge[i].to]; } } void work(int k,int from) { int son=0;f[k]=0; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from) son++,work(edge[i].to,k); if (son==0) f[k]=0; else { for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from) sigmadeep[edge[i].to]+=size[edge[i].to],f[edge[i].to]+=size[edge[i].to]; if (son==1) { int s=0; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from) s=f[edge[i].to]; f[k]=s; } else { int mx1=-1,mx2=-1; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from) if (sigmadeep[edge[i].to]>mx1) mx2=mx1,mx1=sigmadeep[edge[i].to]; else mx2=max(mx2,sigmadeep[edge[i].to]); for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from) mx2=max(mx2,f[edge[i].to]); f[k]=mx2*2; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from) f[k]-=min(mx2,sigmadeep[edge[i].to]); } f[k]=max(f[k],sigmadeep[k]&1); } } //若干个f[]~sigmadeep[]之间的变量,最小化max{x}*2-Σx //枚举maxx 后,后一部分即Σmin(x,sigmadeep) //x应取次大deep signed main() { n=read(); for (int i=1;i<=n;i++) if (getc()=='0') a[i]=0;else a[i]=1; for (int i=1;i<n;i++) { int x=read(),y=read(); addedge(x,y),addedge(y,x); } for (int i=1;i<=n;i++) { memset(deep,0,sizeof(deep)); memset(size,0,sizeof(size)); memset(sigmadeep,0,sizeof(sigmadeep)); memset(f,42,sizeof(f)); dfs(i,i); for (int j=1;j<=n;j++) sigmadeep[j]-=size[j]*deep[j]; if (sigmadeep[i]&1) continue; work(i,i); /*for (int j=1;j<=n;j++) cout<<deep[j]<<' ';cout<<endl; for (int j=1;j<=n;j++) cout<<size[j]<<' ';cout<<endl; for (int j=1;j<=n;j++) cout<<sigmadeep[j]<<' ';cout<<endl; for (int j=1;j<=n;j++) cout<<f[j]<<' ';cout<<endl;cout<<endl;*/ if (f[i]==0) ans=min(ans,sigmadeep[i]/2); } if (ans==inf) cout<<-1;else cout<<ans; return 0; //NOTICE LONG LONG!!!!! } //假设确定根 //f[i]将i子树内的点都移到lca的最小深度之和 //
result:rank 138 rating +56