牛客CSP-S提高组赛前集训营3
A 货物收集
显然是一个二分答案的题。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #include<queue> 7 #define dbg(x) cerr << #x << " = " << x <<endl 8 #define dbg2(x,y) cerr<< #x <<" = "<< x <<" "<< #y <<" = "<< y <<endl 9 using namespace std; 10 typedef long long ll; 11 typedef double db; 12 typedef pair<int,int> pii; 13 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 14 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 15 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;} 16 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;} 17 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;} 18 template<typename T>inline T read(T&x){ 19 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 20 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 21 } 22 const int N=1e6+7,INF=0x3f3f3f3f; 23 int val[N]; 24 int n,L,R,w; 25 struct thxorz{ 26 int head[N],nxt[N<<1],to[N<<1],w[N<<1],tot; 27 inline void add(int x,int y,int z){ 28 to[++tot]=y,nxt[tot]=head[x],head[x]=tot,w[tot]=z; 29 to[++tot]=x,nxt[tot]=head[y],head[y]=tot,w[tot]=z; 30 } 31 }G; 32 #define y G.to[j] 33 inline int dfs(int mid,int x,int fa){ 34 int ret=val[x]; 35 for(register int j=G.head[x];j;j=G.nxt[j])if(y^fa&&G.w[j]<=mid)ret+=dfs(mid,y,x); 36 return ret; 37 } 38 #undef y 39 int main(){//freopen("test.in","r",stdin);freopen("test.out","w",stdout); 40 read(n),read(w);L=INF; 41 for(register int i=2;i<=n;++i)read(val[i]); 42 for(register int i=1,x,y,z;i<n;++i)read(x),read(y),read(z),G.add(x,y,z),MIN(L,z),MAX(R,z); 43 int mid; 44 while(L<R){ 45 mid=L+R>>1; 46 if(dfs(mid,1,0)>=w)R=mid; 47 else L=mid+1; 48 } 49 printf("%d\n",L); 50 return 0; 51 }
B 货物分组
写出一个裸的DP方程。$O(n^3)$.
$f_{i,k}=\min\{f_{j,k-1}+k(sum_i-sum_{j-1})+\max_{j+1\sim i}-\min_{j+1\sim i}\}$
然后因为这个涉及和$k$相关的乘积项,为了简化$k$这一维的状态,采用费用预算的思想,将后面所有组都提前加上一次他们总和。
所以去掉$k$这一维,$O(n^2)$。
$f_i=\min\{f_j-sum_j+\max_{j+1\sim i}-\min_{j+1\sim i}\}$
然后要考虑优化。我DP优化学傻掉了。`````
发现max和min实际上每次都是一个元素控制他前面的一段区间。这个可以用单调栈求出,然后max若干段区间和min若干段区间,每段最值都相同。那么,把方程前两项压入线段树,然后后面的max和min通过修改来完成:每次更新区间的时候,把弹栈的若干区间里的状态去掉他们原来的最值,加上新加入栈的最值。这个就是个区间修改,然后线段树维护最小值,每次转移区间查询即可。$O(n\log n)$。
这里用了标记永久化,实际上是学hkk的,可以发现常数小一些,核心在于在某节点内部的区间上传信息时都要加上自己打的标记。
WA*1:线段树里下标表示对应的状态前两项,单调栈里是最值。。搞反了导致line54~line57出错。。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #include<queue> 7 #define mst(x) memset(x,0,sizeof x) 8 #define dbg(x) cerr << #x << " = " << x <<endl 9 #define dbg2(x,y) cerr<< #x <<" = "<< x <<" "<< #y <<" = "<< y <<endl 10 using namespace std; 11 typedef long long ll; 12 typedef double db; 13 typedef pair<int,int> pii; 14 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 15 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 16 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;} 17 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;} 18 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;} 19 template<typename T>inline T read(T&x){ 20 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 21 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 22 } 23 const int N=1e5+7; 24 const ll INF=1ll<<50; 25 struct segment_tree{ 26 ll minv[N<<2],atag[N<<2]; 27 #define lc i<<1 28 #define rc i<<1|1 29 // segment_tree(){memset(minv,0x3f,sizeof minv);} 30 void update_add(int i,int L,int R,int ql,int qr,ll val){ 31 if(ql<=L&&qr>=R)return minv[i]+=val,atag[i]+=val,void(); 32 int mid=L+R>>1; 33 if(ql<=mid)update_add(lc,L,mid,ql,qr,val); 34 if(qr>mid)update_add(rc,mid+1,R,ql,qr,val); 35 minv[i]=_min(minv[lc],minv[rc])+atag[i]; 36 } 37 ll query_min(int i,int L,int R,int ql,int qr){ 38 if(ql<=L&&qr>=R)return minv[i]; 39 int mid=L+R>>1;ll ret=INF; 40 if(ql<=mid)MIN(ret,query_min(lc,L,mid,ql,qr)); 41 if(qr>mid)MIN(ret,query_min(rc,mid+1,R,ql,qr)); 42 return ret+atag[i]; 43 }//pay attention to the permanent tag... 44 }T; 45 int stkmin[N],stkmax[N],A[N],top1,top2; 46 ll s[N],f[N]; 47 int n,w,lb; 48 49 int main(){//freopen("2.in","r",stdin);//freopen("test.ans","w",stdout); 50 read(n),read(w);stkmin[0]=stkmax[0]=-1; 51 for(register int i=1;i<=n;++i)s[i]=s[i-1]+read(A[i]); 52 for(register int i=1;i<=n;++i){//dbg(i); 53 T.update_add(1,0,n-1,i-1,i-1,f[i-1]+s[n]-s[i-1]); 54 while(top1&&A[stkmin[top1]]>=A[i])T.update_add(1,0,n-1,stkmin[top1-1],stkmin[top1]-1,A[stkmin[top1]]),--top1; 55 T.update_add(1,0,n-1,stkmin[top1],i-1,-A[i]),stkmin[++top1]=i;//dbg(top1); 56 while(top2&&A[stkmax[top2]]<=A[i])T.update_add(1,0,n-1,stkmax[top2-1],stkmax[top2]-1,-A[stkmax[top2]]),--top2; 57 T.update_add(1,0,n-1,stkmax[top2],i-1,A[i]),stkmax[++top2]=i;//dbg(top2); 58 while(s[i]-s[lb]>w)++lb; 59 f[i]=T.query_min(1,0,n-1,lb,i-1); 60 } 61 printf("%lld\n",f[n]); 62 return 0; 63 }
C 地形计算
复杂度计算非常神仙的一个题。其实这题复杂度我也不会算,以下为口胡
顺便学习了一下三元环和四元环的计数。连接1(有复杂度证明),连接2(好像做法不对?),连接3
三元环的话主要就是因为定了向,好处有二:一是每个环只会被计一次,二是复杂度可以利用这个度数大小关系证出来是根号的。
四元环的话,类似。原来的三元环是度数大的向度数小的走,现在为了走四元环保证复杂度,只能每次走深度为2的路(相当于就走了一次三元环),然后所有终点相同、深度为二的里做统计计算。
不过并不是边直接从度数大的走向小的,这样会有四元环被漏计,为了保证每个环被算一次且仅一次,改变走边方法,枚举每一个起点$x$,然后枚举走到的$y$,要求度数$x>y$(相等时按编号),然后再从$y$开始遍历走到的$z$,要求度数也有$z<x$,这样,每个环只会被度数最大的那个点统计到。同时还可以保证复杂度:假设现在$y$度数小于$\sqrt{M}$,则$x$遍历图中每条边,到达的点向外拓展,复杂度$O(M\sqrt{M})$。若$y$度数大于$\sqrt{M}$,则利用度数传递性,$x$度数也应当大于根号,所以得知总的这样的$x$不会超过根号个,所以每一个$y$有不超过根号个入点$x$进来,对于每一个点$y$贡献复杂度$O(\sqrt{M}outdegree_y)$,又因为$\sum outdegree_y\le m$,所以总体也是$O(M\sqrt{M})$的。证毕。其实这也是三元环复杂度的证明方法。
然后这题就算权值就好了。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #include<queue> 7 #define mst(x) memset(x,0,sizeof x) 8 #define dbg(x) cerr << #x << " = " << x <<endl 9 #define dbg2(x,y) cerr<< #x <<" = "<< x <<" "<< #y <<" = "<< y <<endl 10 using namespace std; 11 typedef long long ll; 12 typedef double db; 13 typedef pair<int,int> pii; 14 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 15 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 16 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;} 17 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;} 18 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;} 19 template<typename T>inline T read(T&x){ 20 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 21 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 22 } 23 const int N=1e5+7,P=1e9+7; 24 struct thxorz{ 25 int head[N],nxt[N<<1],to[N<<1],tot; 26 inline void add(int x,int y){ 27 to[++tot]=y,nxt[tot]=head[x],head[x]=tot; 28 to[++tot]=x,nxt[tot]=head[y],head[y]=tot; 29 } 30 }G; 31 int val[N],deg[N],vis[N],sum[N],cnt[N]; 32 int n,m,ans; 33 inline void add(int&a,int b){((a+=b)>=P)&&(a-=P);} 34 inline int mod(int a){return a>=P?a-P:a;} 35 36 int main(){//freopen("test.in","r",stdin);//freopen("test.ans","w",stdout); 37 read(n),read(m); 38 for(register int i=1;i<=n;++i)read(val[i]); 39 for(register int i=1,x,y;i<=m;++i)read(x),read(y),G.add(x,y),++deg[x],++deg[y]; 40 #define y G.to[i] 41 #define z G.to[j] 42 for(register int x=1;x<=n;++x){ 43 for(register int i=G.head[x];i;i=G.nxt[i])if(deg[y]<deg[x]||deg[x]==deg[y]&&y<x){ 44 for(register int j=G.head[y];j;j=G.nxt[j])if(deg[z]<deg[x]||deg[z]==deg[x]&&z<x){ 45 if(vis[z]^x)vis[z]=x,cnt[z]=1,sum[z]=val[y]; 46 else add(ans,mod(cnt[z]*1ll*(val[x]+val[z]+0ll+val[y])%P+sum[z])),++cnt[z],add(sum[z],val[y]); 47 } 48 } 49 } 50 printf("%d\n",ans); 51 return 0; 52 }