Codeforces Round #595 (Div. 3) 题解
A. Yet Another Dividing into Teams
签到,有相邻的数字 ans=2,否则 ans=1
int main(){ scanf("%d",&T); while(T--){ scanf("%d",&n); memset(vis,0,sizeof(vis)); for(int i=1,x;i<=n;i++) scanf("%d",&x),vis[x]=1; int flag=0; for(int i=1;i<=100;i++) if(vis[i]&&(vis[i-1]||vis[i+1])) {flag=1;break;} if(flag==0) printf("1\n"); else printf("2\n"); } return 0; }
B. Books Exchange
找每个顶点处在的环的大小,dfs 行了
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <queue> #include <utility> #define MAXN 200010 using namespace std; const int inf=0x3f3f3f3f; int ans=inf; int T,n,to[MAXN],len[MAXN],vis[MAXN]; void dfs(int u,int dis){ if(vis[u]) {len[u]=dis;return;} vis[u]=1; dfs(to[u],dis+1); len[u]=len[to[u]]; } int main() { scanf("%d",&T); while(T--) { scanf("%d",&n); memset(vis,0,(n+1)*sizeof(int)); memset(len,0,(n+1)*sizeof(int)); for(int i=1;i<=n;i++) scanf("%d",&to[i]); for(int i=1;i<=n;i++) if(!vis[i]) dfs(i,0); for(int i=1;i<=n;i++) printf("%d ",len[i]); printf("\n"); } return 0; }
C. Good Numbers
我的方法是这样的,先将这个数转化为三进制来看。
从 0 位看到最高位,如果第 i 位是 2, 那么就从 i+1 位到更高的位去找一个是 0 的位 j, 将其变成 1, 然后将 j-1 到 0 位的数字全变成 0.
这样扫描一遍之后, 将这个三进制数转化为十进制就是答案了.
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <algorithm> #include <utility> #include <vector> #include <queue> #include <set> #include <map> #define MAXN 100010 #define mid ((l+r)>>1) #define lowbit(x) ((x)&(-x)) using namespace std; typedef long long LL; const int inf=0x3f3f3f3f; const LL INF=0x3f3f3f3f3f3f3f3f; int T; LL n; int num[100]; int main(){ scanf("%d",&T); while(T--){ scanf("%lld",&n); memset(num,0,sizeof(num)); LL k=1,index=0; for(;k<n;k*=3,index+=1); while(k){ while(n>=k) n-=k,num[index]++; k/=3,index-=1; } for(int i=0;i<=64;i++){ if(num[i]==2){ for(int j=i+1;j<=64;j++){ if(num[j]==0){ num[j]=1; for(int k=j-1;k>=0;k--) num[k]=0; break; } } } } LL ans=0; k=1; for(int i=0;i<=64;i++) { if(num[i]) ans+=k; k*=3; } printf("%lld\n",ans); } return 0; }
D. Too Many Segments
我用的贪心+线段树, 结束后发现大神们都用的是优先队列啊, set什么的......
首先我们想要保留尽量多的区间, 那么就要剩下的区间尽量少的重叠, 那么根据这个性质我们可以对区间进行先按右端点从小到大, 若右相等则按左从小到大这样排序.
然后依次插入这些区间, 若一个区间满足这个区间内的点的最大被覆盖次数还不足 k, 那么就将这个区间覆盖下去, 否则这个区间就将被删去, 加入答案. 这个过程需要区间加和查询区间最大值, 用线段树.
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <algorithm> #include <utility> #include <vector> #include <queue> #include <set> #include <map> #define MAXN 200010 #define mid ((l+r)>>1) #define lowbit(x) ((x)&(-x)) using namespace std; typedef long long LL; const int inf=0x3f3f3f3f; const LL INF=0x3f3f3f3f3f3f3f3f; int n,k; struct Seg{ int l,r,id; }p[MAXN]; vector<int> ans; struct SegmentTree{ int tag[MAXN*4],maxv[MAXN*4]; void build(){memset(tag,0,sizeof(tag));memset(maxv,0,sizeof(maxv));} void pushdown(int id,int l,int r){ maxv[id<<1]+=tag[id];tag[id<<1]+=tag[id]; maxv[id<<1|1]+=tag[id];tag[id<<1|1]+=tag[id]; tag[id]=0; } int update(int id,int l,int r,int L,int R,int x){ if(L<=l&&r<=R){maxv[id]+=x;tag[id]+=x;return 0;} if(tag[id]) pushdown(id,l,r); if(L<=mid) update(id<<1,l,mid,L,R,x); if(R>mid) update(id<<1|1,mid+1,r,L,R,x); maxv[id]=max(maxv[id<<1],maxv[id<<1|1]); } int ask(int id,int l,int r,int L,int R){ if(L<=l&&r<=R) return maxv[id]; if(tag[id]) pushdown(id,l,r); int res1=0,res2=0; if(L<=mid) res1=ask(id<<1,l,mid,L,R); if(R>mid) res2=ask(id<<1|1,mid+1,r,L,R); return max(res1,res2); } }tree; bool cmp(Seg a,Seg b){ return a.r<b.r||a.r==b.r&&a.l<b.l; } int main(){ scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) scanf("%d%d",&p[i].l,&p[i].r),p[i].id=i; sort(p+1,p+n+1,cmp); tree.build(); for(int i=1;i<=n;i++){ if(tree.ask(1,1,MAXN,p[i].l,p[i].r)<k) tree.update(1,1,MAXN,p[i].l,p[i].r,1); else ans.push_back(p[i].id); } printf("%d\n",ans.size()); for(int i=0;i<ans.size();i++) printf("%d ",ans[i]); return 0; }
E. By Elevator or Stairs?
这道题其实比 C 题还要简单一点, 就是一个 dp 水题
走了楼梯再做电梯就要等 c 时间, 那么设两个状态, f1i 是走楼梯到达第 i 楼用的最少时间, f2i 是坐电梯到达第 i 楼用的最少时间, 很容易得到状态转移方程:
f1i = min(f1i-1, f2i-1) + a[i-1] , 走楼梯到达第 i 层的最少时间是到达第 i-1 层的最少时间 + a[i -1].
f2i = min(f2i-1, f1i-1 + c) + b[i-1] , 做电梯到达第 i 层的最少时间是坐电梯到达第 i - 1层的最小时间和走楼梯到 i-1 层的最小时间 + 等电梯的时间这两者中的较小值 + b[i - 1]
注意初态 f11 = 0, f21 = inf, 因为一楼不可能是坐电梯到达的嘛.
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXN 200010 using namespace std; const int inf=0x3f3f3f3f; int n,c,a[MAXN],b[MAXN]; int f1[MAXN],f2[MAXN]; int main(){ scanf("%d%d",&n,&c); for(int i=1;i<n;i++) scanf("%d",&a[i]); for(int i=1;i<n;i++) scanf("%d",&b[i]); f1[1]=0;f2[1]=inf; for(int i=2;i<=n;i++){ f1[i]=min(f2[i-1],f1[i-1])+a[i-1]; f2[i]=min(f2[i-1],f1[i-1]+c)+b[i-1]; } for(int i=1;i<=n;i++) printf("%d ",min(f1[i],f2[i])); return 0; }
F. Maximum Weight Subset
介绍一个O(N3)的树形Dp方法.
设状态 f[i,j] 是点 i 到以它为根节点的子树中最近一个选中点的距离大于等于 j 时这颗子树产生的最大贡献.
那么对于 f[i,0] 就是选中点 i 时的最大值,因为每个选中点之间距离要大于 k, 那么转移方程为
$$f\left[ i,0\right] =a\left[ i\right] +\sum ^{son}_{t}f\left[ t,k\right]$$
对于 f[i,w] (0<w<k), 枚举 i 的一个儿子离 i 最近, 它的最近选中点距离当然为 w-1, 为了满足选中点距离大于 k, 并且其他儿子中最近选中点距离 i 不能小于 w, 那么可以得出转移方程:
$$\max _{s\in son}\left\{ f\left[ s,w-1\right] +\sum ^{son}_{t\neq s}\left[ t,\max \left( k-w,w-1\right) \right] \right\}$$
那么注意f[i,j] = max(f[i,j] , f[i,j+1])
#include <iostream> #include <stdlib.h> #include <string.h> #include <stdio.h> using namespace std; int n,k,a[210],f[210][210]; int head[210],to[410],nxt[410],tot=1; void add(int u,int v){to[++tot]=v;nxt[tot]=head[u];head[u]=tot;} void dfs(int u,int rt){ f[u][0]=a[u]; for(int i=head[u];i;i=nxt[i]){ if(to[i]==rt) continue; dfs(to[i],u); } for(int i=head[u];i;i=nxt[i]){ if(to[i]==rt) continue; f[u][0]+=f[to[i]][k]; } for(int w=1;w<=k;w++){ for(int i=head[u];i;i=nxt[i]){ if(to[i]==rt) continue; int temp=f[to[i]][w-1]; for(int j=head[u];j;j=nxt[j]){ if(to[j]==rt||to[j]==to[i]) continue; temp+=f[to[j]][max(k-w,w-1)]; } f[u][w]=max(f[u][w],temp); } } for(int w=k;w>=0;w--) f[u][w]=max(f[u][w+1],f[u][w]); } int main(){ scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1,u,v;i<n;i++){scanf("%d%d",&u,&v);add(u,v);add(v,u);} dfs(1,0); cout<<f[1][0]<<endl; return 0; }