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; }
