AtCoder Regular Contest 098
AtCoder Regular Contest 098
C - Attention
题意:有n个人站成一排,每个人可能面向东或西。
现在要选一个人,让其他人转向所选的那个人。求最小转向次数。
分析:模拟即可。
代码:
#include <cstdio> #include <cstring> #include <algorithm> #include <cstdlib> #include <map> using namespace std; typedef long long ll; #define GG puts("FUCK") #define N 300050 char str[N]; int n,sum; int main() { scanf("%d%s",&n,str+1); int i; for(i=1;i<=n;i++) { if(str[i]=='E') sum++; } int now=0,ans=1<<30; for(i=1;i<=n;i++) { if(str[i]=='E') sum--; ans=min(ans,now+sum); if(str[i]=='W') now++; } printf("%d\n",ans); }
D - Xor Sum 2
题意:给你一个长度为n的序列,求合法连续子序列个数,合法定义为和等于他们异或起来。
分析:可以发现这样的序列长度最多为20,暴力/双指针即可。
代码:
#include <cstdio> #include <cstring> #include <algorithm> #include <cstdlib> #include <map> using namespace std; typedef long long ll; #define GG puts("FUCK") #define N 200050 int n,a[N],h[30],num; void add(int x) { int i; for(i=19;i>=0;i--) if(x&(1<<i)) { h[i]++; if(h[i]==2) num++; } } void del(int x) { int i; for(i=19;i>=0;i--) if(x&(1<<i)) { h[i]--; if(h[i]==1) num--; } } int main() { scanf("%d",&n); int i; for(i=1;i<=n;i++) { scanf("%d",&a[i]); } ll ans=0; int j=1; for(i=1;i<=n;i++) { add(a[i]); while(j<=i&&num) del(a[j]),j++; ans+=(i-j+1); } printf("%lld\n",ans); }
E - Range Minimum Queries
题意:给你一个长度为n的序列,你可以进行Q次操作,每次操作把一段长度为K的连续子序列的最小值删除。
求删掉的数中最大-最小的最小值。
分析:枚举删除的最小值,然后比这个数小的不能被选到,相当于把序列分成几段,每段把可以删的放在一起。
然后拿第Q小的能删的数更新答案。
代码:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define N 2050 int n,a[N]; int K,Q,b[N],c[N],ans=1<<30,tot; void add(int l,int r) { int i; for(i=l;i<=r;i++) b[i]=a[i]; sort(b+l,b+r+1); for(i=l;i<=r-K+1;i++) c[++tot]=b[i]; } void solve(int x) { int i,lst=1; tot=0; for(i=1;i<=n;i++) { if(a[i]<x) { if(lst<=i-1) add(lst,i-1); lst=i+1; } } if(lst<=n) add(lst,n); sort(c+1,c+tot+1); if(tot<Q) return ; ans=min(ans,c[Q]-c[1]); } int main() { int i; scanf("%d%d%d",&n,&K,&Q); for(i=1;i<=n;i++) scanf("%d",&a[i]); for(i=1;i<=n;i++) { solve(a[i]); } printf("%d\n",ans); }
F - Donation
题意:给出一个n个点,m条边的无向图。每个点有权值ai,bi。
你可以任选一个点当做起点,你初始拥有的钱需要大于等于aS。每次移动的时候钱数也要大于等于aT。
你需要对每个点捐出bi的钱,求初始最少有多少钱。
分析:
发现对于每个点在最后一次贡献不会更差。
把A的约束转化一下,设Ci=max(Ai-Bi,0)。这个约束表示任何时候站在i这个点至少要有Ci。
然后贪心的想,Ci大的先遍历显然不会更差。
于是这样:找到C最大的点x,把x删掉后产生了T个连通块G1,G2..GT。
显然有一种最优的方案是贡献x后进入了一个连通块就不再出来。
然后递归每个连通块,用这一层的根连向上一层的根,这样就形成了一棵树。
这棵树满足任意一个点的Ci大于等于子树的点的Cj。
可以DP,设f[x]表示x的子树符合条件的最小初始钱数。
初始值f[x]=s[x]+c[x],其中s[x]表示x的子树b之和。
然后考虑把儿子的贡献拽到x的后面,有f[x]=min(f[x],s[x]-s[to[i]]+max(f[to[i]],c[x]))
代码:
#include <cstdio> #include <cstring> #include <algorithm> #include <vector> using namespace std; typedef long long ll; #define GG puts("FUCK") #define N 100050 int head[N],to[N],nxt[N],fa[N],a[N],b[N],c[N],n,m,vis[N],id[N],cnt; vector<int>v[N]; ll f[N],s[N]; int find(int x) {return fa[x]==x?x:fa[x]=find(fa[x]);} bool cmp(int x,int y) {return c[x]<c[y];} inline void add(int u,int v) { to[++cnt]=v; nxt[cnt]=head[u]; head[u]=cnt; } void dfs(int x) { int i; s[x]=b[x]; for(i=head[x];i;i=nxt[i]) { dfs(to[i]); s[x]+=s[to[i]]; } f[x]=s[x]+c[x]; for(i=head[x];i;i=nxt[i]) { f[x]=min(f[x],s[x]-s[to[i]]+max(f[to[i]],1ll*c[x])); } } int main() { scanf("%d%d",&n,&m); int i,x,y,j; for(i=1;i<=n;i++) { scanf("%d%d",&a[i],&b[i]); c[i]=max(a[i]-b[i],0); fa[i]=id[i]=i; } for(i=1;i<=m;i++) { scanf("%d%d",&x,&y); v[x].push_back(y); v[y].push_back(x); } sort(id+1,id+n+1,cmp); for(i=1;i<=n;i++) { x=id[i]; int lim=v[x].size(); vis[x]=1; for(j=0;j<lim;j++) { y=v[x][j]; if(!vis[y]) continue; y=find(y); if(x!=y) { fa[y]=x; add(x,y); } } } dfs(id[n]); printf("%lld\n",f[id[n]]); }